pro-editor-schema 1.5.5-7 → 1.5.5-8

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
@@ -182,7 +182,7 @@ From `src/index.ts`, consumers can import:
182
182
  - **Schema helpers:** `findSchemaByCode`, `findSchemaByElement`, `findAttributeDefinitionByName`, `getAttributeName`, `isAttributeRestricted`, `isTrackChangesSchema`
183
183
  - **DOM helpers:** `createElement`, `closestElementBySchema`, `queryElementBySchema`, `queryAllElementsBySchema`, `getElementLinks`, `getStore`, `isDeletedElement`
184
184
  - **Ignored nodes:** `isIgnoredAttribute`, `isIgnoredElement`, `IGNORED_ATTRIBUTES`, `IGNORED_ELEMENT_SELECTORS`
185
- - **Validation:** `validateXml`
185
+ - **Validation:** `validateXml`, `validateXmlAsync`
186
186
  - **Shared constants:** `ElementLabel`, global attribute constants, dependents forbidden elements
187
187
 
188
188
  ## Usage examples
@@ -256,6 +256,28 @@ It also runs per-element checks for:
256
256
 
257
257
  Schema-based attribute and child validation only runs for codes listed in `schemasToValidateAgainst`.
258
258
 
259
+ ### Validate a large document without freezing the page
260
+
261
+ ```ts
262
+ import { validateXmlAsync } from "pro-editor-schema";
263
+
264
+ const controller = new AbortController();
265
+ const errors = await validateXmlAsync(
266
+ rootElement.cloneNode(true) as Element,
267
+ elementsToLink,
268
+ linkElements,
269
+ ["para", "heading"],
270
+ { signal: controller.signal },
271
+ );
272
+ ```
273
+
274
+ `validateXmlAsync` returns the same issues as `validateXml`, in the same order, but works in short slices and hands the main thread back between them (`scheduler.yield()` where available, otherwise `setTimeout`).
275
+
276
+ - `signal` — aborting it rejects the promise with the signal's reason, for example when a newer edit makes the run outdated.
277
+ - `timeBudgetMs` — how long to work before yielding. Defaults to 8 ms.
278
+
279
+ Pass a copy of the document if it can change while validation runs.
280
+
259
281
  Listing `referenceBibliomixed` checks each bibliography entry against the schema of its reference type instead (see [Reference schemas](#reference-schemas)).
260
282
 
261
283
  ## How validation works
package/dist/index.d.mts CHANGED
@@ -1002,7 +1002,7 @@ type ValidationError = {
1002
1002
 
1003
1003
  /**
1004
1004
  * Validates the XML document against the schema.
1005
-
1005
+
1006
1006
  * @param xml - The XML document to validate.
1007
1007
  * @param elementsToLink - The elements IDs that are linked to.
1008
1008
  * @param linkElements - The elements IDs that are linked to.
@@ -1011,11 +1011,29 @@ type ValidationError = {
1011
1011
  * @returns The validation errors.
1012
1012
  */
1013
1013
  declare const validateXml: (xml: Element, elementsToLink: string[], linkElements: string[], schemasToValidateAgainst: SchemaCode[]) => ValidationError[];
1014
+ type ValidateXmlAsyncOptions = {
1015
+ /** Rejects with the signal's reason once aborted. */
1016
+ signal?: AbortSignal;
1017
+ /** How long to work before handing the main thread back. Defaults to 8 ms. */
1018
+ timeBudgetMs?: number;
1019
+ };
1020
+ /**
1021
+ * Same result as `validateXml`, but works in short slices and hands the main
1022
+ * thread back between them, so large documents do not freeze the page.
1023
+ *
1024
+ * Pass a copy of the document if it can change while validation runs.
1025
+ */
1026
+ declare const validateXmlAsync: (xml: Element, elementsToLink: string[], linkElements: string[], schemasToValidateAgainst: SchemaCode[], { signal, timeBudgetMs }?: ValidateXmlAsyncOptions) => Promise<ValidationError[]>;
1027
+
1028
+ type LinkIds = readonly string[] | ReadonlySet<string>;
1014
1029
 
1015
- declare const findElementLinkedTo: (linkend: string, elementsToLink: string[]) => boolean;
1030
+ declare const findElementLinkedTo: (linkend: string, elementsToLink: LinkIds) => boolean;
1016
1031
  declare const LINK_TO_CROSS_CHECK: string[];
1017
1032
  declare const LINK_BLOCKER_ROLES: string[];
1018
- declare const validateLinkend: (linkElement: Element, elementsToLink: string[]) => ValidationError[];
1033
+ declare const validateLinkend: (linkElement: Element, elementsToLink: LinkIds) => ValidationError[];
1034
+
1035
+ /** Work split into steps: each `yield` is a point where a caller may pause. */
1036
+ type Steps<T> = Generator<void, T, void>;
1019
1037
 
1020
1038
  declare const POSSIBLE_WEBLINK_CLASS = "possible-weblink";
1021
1039
  declare const POSSIBLE_WEBLINK_EXCLUDE_SELECTORS: string[];
@@ -1025,6 +1043,7 @@ type PossibleWeblink = {
1025
1043
  };
1026
1044
  declare const getPossibleWeblinks: (root: Element) => Map<string, PossibleWeblink>;
1027
1045
  declare const markPossibleWeblinks: (editor: HTMLElement) => Map<string, PossibleWeblink>;
1046
+ declare function validatePossibleWeblinksSteps(root: Element): Steps<ValidationError[]>;
1028
1047
  declare const validatePossibleWeblinks: (root: Element) => ValidationError[];
1029
1048
  declare const clearWeblinkMatches: (editor: HTMLElement) => void;
1030
1049
 
@@ -1038,7 +1057,7 @@ declare const validateFootnoteNumber: (footnoteElement: Element) => ValidationEr
1038
1057
  declare const validateConsecutiveFootnotes: (root: Element | Document) => ValidationError[];
1039
1058
  declare const validateConsecutiveFootnoteCues: (root: Element | Document) => ValidationError[];
1040
1059
 
1041
- declare const validateElementToLinkTo: (element: Element, linkElements: string[]) => ValidationError[];
1060
+ declare const validateElementToLinkTo: (element: Element, linkElements: LinkIds) => ValidationError[];
1042
1061
 
1043
1062
  declare const validateElementAgainstSchema: (element: Element, schema: Schema) => ValidationError[];
1044
1063
  declare const getEffectiveChildElements: (element: Element) => Element[];
@@ -1122,4 +1141,4 @@ declare const REFERENCE_PART_SELECTOR: string;
1122
1141
  declare const getReferenceParts: (reference: Element) => HTMLElement[];
1123
1142
  declare const getReferenceFieldElements: (reference: Element) => ReferenceFieldElement[];
1124
1143
 
1125
- export { ALIGNMENT_ATTRIBUTE, ATOM_SCHEMAS, type AllowedChildren, type AtomSchemaCode, type AttributeDefinition, type BibliomsetRelation, DEPENDENT_FORBIDDEN_ELEMENTS, ELEMENT_SCHEMAS, ELE_ID_ATTRIBUTE, ElementLabel, type ElementSchemaCode, FIGURE_PLACEMENT_ATTRIBUTE, type FreeAttribute, ID_ATTRS, IGNORED_ATTRIBUTES, IGNORED_ATTRIBUTES_STARTS_WITH, IGNORED_ELEMENT_SELECTORS, INDENT_TYPE_ATTRIBUTE, INLINE_ELEMENT_SCHEMAS, type InlineSchemaCode, LAYOUT_ATTRIBUTES, LAYOUT_CELL_WIDTH_ATTRIBUTE, LAYOUT_TABLE_ORIENTATION_ATTRIBUTE, LAYOUT_TABLE_PLACEMENT_ATTRIBUTE, LAYOUT_USER_ATTRIBUTE, LEFT_INDENT_ATTRIBUTE, LINKEND_ATTRIBUTE, LINK_BLOCKER_ROLES, LINK_TO_CROSS_CHECK, POSSIBLE_WEBLINK_CLASS, POSSIBLE_WEBLINK_EXCLUDE_SELECTORS, REFERENCE_FIELD_SCHEMAS, REFERENCE_PART_SELECTOR, REFERENCE_TYPE_SCHEMAS, RIGHT_INDENT_ATTRIBUTE, type ReferenceElementReplacement, type ReferenceFieldCode, type ReferenceFieldElement, type ReferenceFieldSchema, type ReferenceMarkupIssue, type ReferenceMarkupRule, type ReferenceStructureIssue, type ReferenceTypeCode, type ReferenceTypeSchema, type RequiredReferenceField, type RestrictedAttribute, SCHEMAS, SPACE_ABOVE_ATTRIBUTE, SPACE_BELOW_ATTRIBUTE, SPECIAL_INDENT_ATTRIBUTE, STORE_ATTRIBUTE, STYLE_NAME_ATTRIBUTE, type Schema, type SchemaAttributes, type SchemaCode, type SchemaKind, type SchemasByCode, TRACK_CHANGES_ATTRS, TRACK_CHANGES_ATTR_DATA_DATE, TRACK_CHANGES_ATTR_DATA_TIME, TRACK_CHANGES_ATTR_DATA_USERID, TRACK_CHANGES_ATTR_DATA_USERNAME, VALIDATION_IGNORED_ATTRIBUTE, type ValidationError, type ValidationErrorKind, type ValidationErrorLevel, XMLID_ATTRIBUTE, clearWeblinkMatches, closestElementBySchema, createElement, extractFootnoteNumber, findAttributeDefinitionByName, findElementLinkedTo, findReferenceFieldByElement, findReferenceTypeSchemaByElement, findSchemaByCode, findSchemaByElement, getAdjacentElement, getAttributeName, getEffectiveChildElements, getElementLinks, getPossibleWeblinks, getReferenceFieldElements, getReferenceParts, getReferenceStructureIssueElements, getStore, isAttributeRestricted, isDeletedElement, isIgnoredAttribute, isIgnoredElement, isTrackChangesSchema, markPossibleWeblinks, queryAllElementsBySchema, queryElementBySchema, validateAttributes, validateConsecutiveFootnoteCues, validateConsecutiveFootnotes, validateElementAgainstSchema, validateElementToLinkTo, validateFootnoteNumber, validateHierarchy, validateLinkend, validateOlink, validatePossibleWeblinks, validateReferenceStructure, validateXml };
1144
+ export { ALIGNMENT_ATTRIBUTE, ATOM_SCHEMAS, type AllowedChildren, type AtomSchemaCode, type AttributeDefinition, type BibliomsetRelation, DEPENDENT_FORBIDDEN_ELEMENTS, ELEMENT_SCHEMAS, ELE_ID_ATTRIBUTE, ElementLabel, type ElementSchemaCode, FIGURE_PLACEMENT_ATTRIBUTE, type FreeAttribute, ID_ATTRS, IGNORED_ATTRIBUTES, IGNORED_ATTRIBUTES_STARTS_WITH, IGNORED_ELEMENT_SELECTORS, INDENT_TYPE_ATTRIBUTE, INLINE_ELEMENT_SCHEMAS, type InlineSchemaCode, LAYOUT_ATTRIBUTES, LAYOUT_CELL_WIDTH_ATTRIBUTE, LAYOUT_TABLE_ORIENTATION_ATTRIBUTE, LAYOUT_TABLE_PLACEMENT_ATTRIBUTE, LAYOUT_USER_ATTRIBUTE, LEFT_INDENT_ATTRIBUTE, LINKEND_ATTRIBUTE, LINK_BLOCKER_ROLES, LINK_TO_CROSS_CHECK, POSSIBLE_WEBLINK_CLASS, POSSIBLE_WEBLINK_EXCLUDE_SELECTORS, REFERENCE_FIELD_SCHEMAS, REFERENCE_PART_SELECTOR, REFERENCE_TYPE_SCHEMAS, RIGHT_INDENT_ATTRIBUTE, type ReferenceElementReplacement, type ReferenceFieldCode, type ReferenceFieldElement, type ReferenceFieldSchema, type ReferenceMarkupIssue, type ReferenceMarkupRule, type ReferenceStructureIssue, type ReferenceTypeCode, type ReferenceTypeSchema, type RequiredReferenceField, type RestrictedAttribute, SCHEMAS, SPACE_ABOVE_ATTRIBUTE, SPACE_BELOW_ATTRIBUTE, SPECIAL_INDENT_ATTRIBUTE, STORE_ATTRIBUTE, STYLE_NAME_ATTRIBUTE, type Schema, type SchemaAttributes, type SchemaCode, type SchemaKind, type SchemasByCode, TRACK_CHANGES_ATTRS, TRACK_CHANGES_ATTR_DATA_DATE, TRACK_CHANGES_ATTR_DATA_TIME, TRACK_CHANGES_ATTR_DATA_USERID, TRACK_CHANGES_ATTR_DATA_USERNAME, VALIDATION_IGNORED_ATTRIBUTE, type ValidateXmlAsyncOptions, type ValidationError, type ValidationErrorKind, type ValidationErrorLevel, XMLID_ATTRIBUTE, clearWeblinkMatches, closestElementBySchema, createElement, extractFootnoteNumber, findAttributeDefinitionByName, findElementLinkedTo, findReferenceFieldByElement, findReferenceTypeSchemaByElement, findSchemaByCode, findSchemaByElement, getAdjacentElement, getAttributeName, getEffectiveChildElements, getElementLinks, getPossibleWeblinks, getReferenceFieldElements, getReferenceParts, getReferenceStructureIssueElements, getStore, isAttributeRestricted, isDeletedElement, isIgnoredAttribute, isIgnoredElement, isTrackChangesSchema, markPossibleWeblinks, queryAllElementsBySchema, queryElementBySchema, validateAttributes, validateConsecutiveFootnoteCues, validateConsecutiveFootnotes, validateElementAgainstSchema, validateElementToLinkTo, validateFootnoteNumber, validateHierarchy, validateLinkend, validateOlink, validatePossibleWeblinks, validatePossibleWeblinksSteps, validateReferenceStructure, validateXml, validateXmlAsync };
package/dist/index.d.ts CHANGED
@@ -1002,7 +1002,7 @@ type ValidationError = {
1002
1002
 
1003
1003
  /**
1004
1004
  * Validates the XML document against the schema.
1005
-
1005
+
1006
1006
  * @param xml - The XML document to validate.
1007
1007
  * @param elementsToLink - The elements IDs that are linked to.
1008
1008
  * @param linkElements - The elements IDs that are linked to.
@@ -1011,11 +1011,29 @@ type ValidationError = {
1011
1011
  * @returns The validation errors.
1012
1012
  */
1013
1013
  declare const validateXml: (xml: Element, elementsToLink: string[], linkElements: string[], schemasToValidateAgainst: SchemaCode[]) => ValidationError[];
1014
+ type ValidateXmlAsyncOptions = {
1015
+ /** Rejects with the signal's reason once aborted. */
1016
+ signal?: AbortSignal;
1017
+ /** How long to work before handing the main thread back. Defaults to 8 ms. */
1018
+ timeBudgetMs?: number;
1019
+ };
1020
+ /**
1021
+ * Same result as `validateXml`, but works in short slices and hands the main
1022
+ * thread back between them, so large documents do not freeze the page.
1023
+ *
1024
+ * Pass a copy of the document if it can change while validation runs.
1025
+ */
1026
+ declare const validateXmlAsync: (xml: Element, elementsToLink: string[], linkElements: string[], schemasToValidateAgainst: SchemaCode[], { signal, timeBudgetMs }?: ValidateXmlAsyncOptions) => Promise<ValidationError[]>;
1027
+
1028
+ type LinkIds = readonly string[] | ReadonlySet<string>;
1014
1029
 
1015
- declare const findElementLinkedTo: (linkend: string, elementsToLink: string[]) => boolean;
1030
+ declare const findElementLinkedTo: (linkend: string, elementsToLink: LinkIds) => boolean;
1016
1031
  declare const LINK_TO_CROSS_CHECK: string[];
1017
1032
  declare const LINK_BLOCKER_ROLES: string[];
1018
- declare const validateLinkend: (linkElement: Element, elementsToLink: string[]) => ValidationError[];
1033
+ declare const validateLinkend: (linkElement: Element, elementsToLink: LinkIds) => ValidationError[];
1034
+
1035
+ /** Work split into steps: each `yield` is a point where a caller may pause. */
1036
+ type Steps<T> = Generator<void, T, void>;
1019
1037
 
1020
1038
  declare const POSSIBLE_WEBLINK_CLASS = "possible-weblink";
1021
1039
  declare const POSSIBLE_WEBLINK_EXCLUDE_SELECTORS: string[];
@@ -1025,6 +1043,7 @@ type PossibleWeblink = {
1025
1043
  };
1026
1044
  declare const getPossibleWeblinks: (root: Element) => Map<string, PossibleWeblink>;
1027
1045
  declare const markPossibleWeblinks: (editor: HTMLElement) => Map<string, PossibleWeblink>;
1046
+ declare function validatePossibleWeblinksSteps(root: Element): Steps<ValidationError[]>;
1028
1047
  declare const validatePossibleWeblinks: (root: Element) => ValidationError[];
1029
1048
  declare const clearWeblinkMatches: (editor: HTMLElement) => void;
1030
1049
 
@@ -1038,7 +1057,7 @@ declare const validateFootnoteNumber: (footnoteElement: Element) => ValidationEr
1038
1057
  declare const validateConsecutiveFootnotes: (root: Element | Document) => ValidationError[];
1039
1058
  declare const validateConsecutiveFootnoteCues: (root: Element | Document) => ValidationError[];
1040
1059
 
1041
- declare const validateElementToLinkTo: (element: Element, linkElements: string[]) => ValidationError[];
1060
+ declare const validateElementToLinkTo: (element: Element, linkElements: LinkIds) => ValidationError[];
1042
1061
 
1043
1062
  declare const validateElementAgainstSchema: (element: Element, schema: Schema) => ValidationError[];
1044
1063
  declare const getEffectiveChildElements: (element: Element) => Element[];
@@ -1122,4 +1141,4 @@ declare const REFERENCE_PART_SELECTOR: string;
1122
1141
  declare const getReferenceParts: (reference: Element) => HTMLElement[];
1123
1142
  declare const getReferenceFieldElements: (reference: Element) => ReferenceFieldElement[];
1124
1143
 
1125
- export { ALIGNMENT_ATTRIBUTE, ATOM_SCHEMAS, type AllowedChildren, type AtomSchemaCode, type AttributeDefinition, type BibliomsetRelation, DEPENDENT_FORBIDDEN_ELEMENTS, ELEMENT_SCHEMAS, ELE_ID_ATTRIBUTE, ElementLabel, type ElementSchemaCode, FIGURE_PLACEMENT_ATTRIBUTE, type FreeAttribute, ID_ATTRS, IGNORED_ATTRIBUTES, IGNORED_ATTRIBUTES_STARTS_WITH, IGNORED_ELEMENT_SELECTORS, INDENT_TYPE_ATTRIBUTE, INLINE_ELEMENT_SCHEMAS, type InlineSchemaCode, LAYOUT_ATTRIBUTES, LAYOUT_CELL_WIDTH_ATTRIBUTE, LAYOUT_TABLE_ORIENTATION_ATTRIBUTE, LAYOUT_TABLE_PLACEMENT_ATTRIBUTE, LAYOUT_USER_ATTRIBUTE, LEFT_INDENT_ATTRIBUTE, LINKEND_ATTRIBUTE, LINK_BLOCKER_ROLES, LINK_TO_CROSS_CHECK, POSSIBLE_WEBLINK_CLASS, POSSIBLE_WEBLINK_EXCLUDE_SELECTORS, REFERENCE_FIELD_SCHEMAS, REFERENCE_PART_SELECTOR, REFERENCE_TYPE_SCHEMAS, RIGHT_INDENT_ATTRIBUTE, type ReferenceElementReplacement, type ReferenceFieldCode, type ReferenceFieldElement, type ReferenceFieldSchema, type ReferenceMarkupIssue, type ReferenceMarkupRule, type ReferenceStructureIssue, type ReferenceTypeCode, type ReferenceTypeSchema, type RequiredReferenceField, type RestrictedAttribute, SCHEMAS, SPACE_ABOVE_ATTRIBUTE, SPACE_BELOW_ATTRIBUTE, SPECIAL_INDENT_ATTRIBUTE, STORE_ATTRIBUTE, STYLE_NAME_ATTRIBUTE, type Schema, type SchemaAttributes, type SchemaCode, type SchemaKind, type SchemasByCode, TRACK_CHANGES_ATTRS, TRACK_CHANGES_ATTR_DATA_DATE, TRACK_CHANGES_ATTR_DATA_TIME, TRACK_CHANGES_ATTR_DATA_USERID, TRACK_CHANGES_ATTR_DATA_USERNAME, VALIDATION_IGNORED_ATTRIBUTE, type ValidationError, type ValidationErrorKind, type ValidationErrorLevel, XMLID_ATTRIBUTE, clearWeblinkMatches, closestElementBySchema, createElement, extractFootnoteNumber, findAttributeDefinitionByName, findElementLinkedTo, findReferenceFieldByElement, findReferenceTypeSchemaByElement, findSchemaByCode, findSchemaByElement, getAdjacentElement, getAttributeName, getEffectiveChildElements, getElementLinks, getPossibleWeblinks, getReferenceFieldElements, getReferenceParts, getReferenceStructureIssueElements, getStore, isAttributeRestricted, isDeletedElement, isIgnoredAttribute, isIgnoredElement, isTrackChangesSchema, markPossibleWeblinks, queryAllElementsBySchema, queryElementBySchema, validateAttributes, validateConsecutiveFootnoteCues, validateConsecutiveFootnotes, validateElementAgainstSchema, validateElementToLinkTo, validateFootnoteNumber, validateHierarchy, validateLinkend, validateOlink, validatePossibleWeblinks, validateReferenceStructure, validateXml };
1144
+ export { ALIGNMENT_ATTRIBUTE, ATOM_SCHEMAS, type AllowedChildren, type AtomSchemaCode, type AttributeDefinition, type BibliomsetRelation, DEPENDENT_FORBIDDEN_ELEMENTS, ELEMENT_SCHEMAS, ELE_ID_ATTRIBUTE, ElementLabel, type ElementSchemaCode, FIGURE_PLACEMENT_ATTRIBUTE, type FreeAttribute, ID_ATTRS, IGNORED_ATTRIBUTES, IGNORED_ATTRIBUTES_STARTS_WITH, IGNORED_ELEMENT_SELECTORS, INDENT_TYPE_ATTRIBUTE, INLINE_ELEMENT_SCHEMAS, type InlineSchemaCode, LAYOUT_ATTRIBUTES, LAYOUT_CELL_WIDTH_ATTRIBUTE, LAYOUT_TABLE_ORIENTATION_ATTRIBUTE, LAYOUT_TABLE_PLACEMENT_ATTRIBUTE, LAYOUT_USER_ATTRIBUTE, LEFT_INDENT_ATTRIBUTE, LINKEND_ATTRIBUTE, LINK_BLOCKER_ROLES, LINK_TO_CROSS_CHECK, POSSIBLE_WEBLINK_CLASS, POSSIBLE_WEBLINK_EXCLUDE_SELECTORS, REFERENCE_FIELD_SCHEMAS, REFERENCE_PART_SELECTOR, REFERENCE_TYPE_SCHEMAS, RIGHT_INDENT_ATTRIBUTE, type ReferenceElementReplacement, type ReferenceFieldCode, type ReferenceFieldElement, type ReferenceFieldSchema, type ReferenceMarkupIssue, type ReferenceMarkupRule, type ReferenceStructureIssue, type ReferenceTypeCode, type ReferenceTypeSchema, type RequiredReferenceField, type RestrictedAttribute, SCHEMAS, SPACE_ABOVE_ATTRIBUTE, SPACE_BELOW_ATTRIBUTE, SPECIAL_INDENT_ATTRIBUTE, STORE_ATTRIBUTE, STYLE_NAME_ATTRIBUTE, type Schema, type SchemaAttributes, type SchemaCode, type SchemaKind, type SchemasByCode, TRACK_CHANGES_ATTRS, TRACK_CHANGES_ATTR_DATA_DATE, TRACK_CHANGES_ATTR_DATA_TIME, TRACK_CHANGES_ATTR_DATA_USERID, TRACK_CHANGES_ATTR_DATA_USERNAME, VALIDATION_IGNORED_ATTRIBUTE, type ValidateXmlAsyncOptions, type ValidationError, type ValidationErrorKind, type ValidationErrorLevel, XMLID_ATTRIBUTE, clearWeblinkMatches, closestElementBySchema, createElement, extractFootnoteNumber, findAttributeDefinitionByName, findElementLinkedTo, findReferenceFieldByElement, findReferenceTypeSchemaByElement, findSchemaByCode, findSchemaByElement, getAdjacentElement, getAttributeName, getEffectiveChildElements, getElementLinks, getPossibleWeblinks, getReferenceFieldElements, getReferenceParts, getReferenceStructureIssueElements, getStore, isAttributeRestricted, isDeletedElement, isIgnoredAttribute, isIgnoredElement, isTrackChangesSchema, markPossibleWeblinks, queryAllElementsBySchema, queryElementBySchema, validateAttributes, validateConsecutiveFootnoteCues, validateConsecutiveFootnotes, validateElementAgainstSchema, validateElementToLinkTo, validateFootnoteNumber, validateHierarchy, validateLinkend, validateOlink, validatePossibleWeblinks, validatePossibleWeblinksSteps, validateReferenceStructure, validateXml, validateXmlAsync };
package/dist/index.js CHANGED
@@ -108,8 +108,10 @@ __export(index_exports, {
108
108
  validateLinkend: () => validateLinkend,
109
109
  validateOlink: () => validateOlink,
110
110
  validatePossibleWeblinks: () => validatePossibleWeblinks,
111
+ validatePossibleWeblinksSteps: () => validatePossibleWeblinksSteps,
111
112
  validateReferenceStructure: () => validateReferenceStructure,
112
- validateXml: () => validateXml
113
+ validateXml: () => validateXml,
114
+ validateXmlAsync: () => validateXmlAsync
113
115
  });
114
116
  module.exports = __toCommonJS(index_exports);
115
117
 
@@ -2845,12 +2847,19 @@ var findAttributeDefinitionByName = (definitions, name) => {
2845
2847
  var findSchemaByCode = (code) => SCHEMAS[code];
2846
2848
 
2847
2849
  // src/schema/helpers/findSchemaByElement.ts
2848
- var findSchemaByElement = (element) => {
2849
- const schema = Object.values(SCHEMAS).find(
2850
- (schema2) => matchesSchemaTag(element, schema2.tag) && (!schema2.matchesElement || schema2.matchesElement(element))
2851
- );
2852
- return schema;
2850
+ var schemasByTag;
2851
+ var getSchemasByTag = () => {
2852
+ if (schemasByTag) return schemasByTag;
2853
+ schemasByTag = /* @__PURE__ */ new Map();
2854
+ for (const schema of Object.values(SCHEMAS)) {
2855
+ const tag = schema.tag.toUpperCase();
2856
+ const schemas = schemasByTag.get(tag);
2857
+ if (schemas) schemas.push(schema);
2858
+ else schemasByTag.set(tag, [schema]);
2859
+ }
2860
+ return schemasByTag;
2853
2861
  };
2862
+ var findSchemaByElement = (element) => getSchemasByTag().get(element.tagName.toUpperCase())?.find((schema) => !schema.matchesElement || schema.matchesElement(element));
2854
2863
 
2855
2864
  // src/schema/helpers/isAttributeRestricted.ts
2856
2865
  var isAttributeRestricted = (attr) => typeof attr !== "string";
@@ -3394,7 +3403,8 @@ var IGNORED_ELEMENT_SELECTORS = [
3394
3403
  "span.ref-drag",
3395
3404
  "span#dummySpan"
3396
3405
  ];
3397
- var isIgnoredElement = (element) => IGNORED_ELEMENT_SELECTORS.some((selector) => element.matches(selector));
3406
+ var IGNORED_ELEMENT_SELECTOR = IGNORED_ELEMENT_SELECTORS.join(", ");
3407
+ var isIgnoredElement = (element) => element.tagName.toUpperCase() === "SPAN" && element.matches(IGNORED_ELEMENT_SELECTOR);
3398
3408
 
3399
3409
  // src/validation/helpers/isIgnoredValidation.ts
3400
3410
  var IGNORED_VALIDATION_ELEMENT_SELECTORS = [
@@ -3553,10 +3563,11 @@ var validateConsecutiveFootnoteCues = (root) => {
3553
3563
  return errors;
3554
3564
  };
3555
3565
 
3566
+ // src/validation/helpers/hasLinkId.ts
3567
+ var hasLinkId = (ids, id) => "has" in ids ? ids.has(id) : ids.includes(id);
3568
+
3556
3569
  // src/validation/validateLinkend.ts
3557
- var findElementLinkedTo = (linkend, elementsToLink) => {
3558
- return elementsToLink.includes(linkend);
3559
- };
3570
+ var findElementLinkedTo = (linkend, elementsToLink) => hasLinkId(elementsToLink, linkend);
3560
3571
  var LINK_TO_CROSS_CHECK = [
3561
3572
  TABLE_LINK_ROLE,
3562
3573
  FIGURE_LINK_ROLE,
@@ -3777,9 +3788,7 @@ var getExtraInstruction = (schema, childSchema) => {
3777
3788
  };
3778
3789
 
3779
3790
  // src/validation/validateElementToLinkTo.ts
3780
- var findElementLinkedTo2 = (xmlid, linkElements) => {
3781
- return linkElements.includes(xmlid);
3782
- };
3791
+ var findElementLinkedTo2 = (xmlid, linkElements) => hasLinkId(linkElements, xmlid);
3783
3792
  var isFootnote = (element) => {
3784
3793
  return matchesSchemaTag(element, FOOTNOTE_SCHEMA.tag);
3785
3794
  };
@@ -3849,6 +3858,16 @@ var validateElementToLinkTo = (element, linkElements) => {
3849
3858
 
3850
3859
  // src/validation/validateWeblink.ts
3851
3860
  var import_mark2 = __toESM(require("mark.js"));
3861
+
3862
+ // src/validation/helpers/steps.ts
3863
+ var runSteps = (steps) => {
3864
+ for (; ; ) {
3865
+ const step = steps.next();
3866
+ if (step.done) return step.value;
3867
+ }
3868
+ };
3869
+
3870
+ // src/validation/validateWeblink.ts
3852
3871
  var EDGE_PUNCTUATION = `:;.,\\-\u2013\u2014\u2012_(){}[\\]!'"+=\xAB\xBB\u2039\u203A\u201C\u201D\u2018\u2019<>`;
3853
3872
  var WEB_URI_PATTERN = `(?:(?:https?):\\/\\/|www\\.)(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\\.)+[a-zA-Z]{2,}(?::\\d{1,5})?(?:\\/(?:[^\\s]*[^\\s${EDGE_PUNCTUATION}])?)?`;
3854
3873
  var WEB_URI_IN_TEXT_REGEX = new RegExp(WEB_URI_PATTERN, "gi");
@@ -3929,11 +3948,62 @@ var markPossibleWeblinks = (editor) => {
3929
3948
  });
3930
3949
  return possibleWeblinks;
3931
3950
  };
3932
- var validatePossibleWeblinks = (root) => {
3951
+ var TEXT_NODES_PER_STEP = 256;
3952
+ var MARK_EXCLUDED_PARENT_SELECTOR = [
3953
+ ...POSSIBLE_WEBLINK_EXCLUDE_SELECTORS,
3954
+ "script",
3955
+ "style",
3956
+ "title",
3957
+ "head",
3958
+ "html"
3959
+ ].join(", ");
3960
+ var createWeblinkMatchElement = (ownerDocument, text, storeId) => {
3961
+ const element = ownerDocument.createElement("mark");
3962
+ element.setAttribute("data-markjs", "true");
3963
+ element.setAttribute("class", POSSIBLE_WEBLINK_CLASS);
3964
+ element.setAttribute(STORE_ATTRIBUTE, storeId);
3965
+ element.textContent = text;
3966
+ return element;
3967
+ };
3968
+ function* findPossibleWeblinks(root) {
3969
+ const possibleWeblinks = /* @__PURE__ */ new Map();
3970
+ const ownerDocument = root.ownerDocument;
3971
+ const walker = ownerDocument.createTreeWalker(root, NodeFilter.SHOW_TEXT);
3972
+ const regex = new RegExp(WEB_URI_PATTERN, "gi");
3973
+ let nodesSinceStep = 0;
3974
+ for (let node = walker.nextNode(); node; node = walker.nextNode()) {
3975
+ if (++nodesSinceStep === TEXT_NODES_PER_STEP) {
3976
+ nodesSinceStep = 0;
3977
+ yield;
3978
+ }
3979
+ const text = node.textContent ?? "";
3980
+ if (!text) continue;
3981
+ const parent = node.parentNode;
3982
+ if (parent instanceof Element && parent.matches(MARK_EXCLUDED_PARENT_SELECTOR)) {
3983
+ continue;
3984
+ }
3985
+ regex.lastIndex = 0;
3986
+ for (let match = regex.exec(text); match; match = regex.exec(text)) {
3987
+ const storeId = getWeblinkMatchId(possibleWeblinks.size);
3988
+ possibleWeblinks.set(storeId, {
3989
+ weblink: match[0],
3990
+ elements: [createWeblinkMatchElement(ownerDocument, match[0], storeId)]
3991
+ });
3992
+ }
3993
+ }
3994
+ return possibleWeblinks;
3995
+ }
3996
+ var hasWeblinkMatches = (root) => root.getElementsByClassName(POSSIBLE_WEBLINK_CLASS).length > 0;
3997
+ function* validatePossibleWeblinksSteps(root) {
3933
3998
  const issues = [];
3934
- const cloneRoot = root.cloneNode(true);
3935
- clearWeblinkMatches(cloneRoot);
3936
- const possibleWeblinks = markPossibleWeblinks(cloneRoot);
3999
+ let possibleWeblinks;
4000
+ if (hasWeblinkMatches(root)) {
4001
+ const cloneRoot = root.cloneNode(true);
4002
+ clearWeblinkMatches(cloneRoot);
4003
+ possibleWeblinks = markPossibleWeblinks(cloneRoot);
4004
+ } else {
4005
+ possibleWeblinks = yield* findPossibleWeblinks(root);
4006
+ }
3937
4007
  for (const [storeId, { elements }] of Array.from(possibleWeblinks)) {
3938
4008
  issues.push(
3939
4009
  createValidationIssue(null, {
@@ -3947,7 +4017,8 @@ var validatePossibleWeblinks = (root) => {
3947
4017
  );
3948
4018
  }
3949
4019
  return issues;
3950
- };
4020
+ }
4021
+ var validatePossibleWeblinks = (root) => runSteps(validatePossibleWeblinksSteps(root));
3951
4022
  var clearWeblinkMatches = (editor) => {
3952
4023
  const instance = new import_mark2.default(editor);
3953
4024
  instance.unmark({
@@ -4404,66 +4475,106 @@ var validateReference = (reference) => {
4404
4475
  };
4405
4476
 
4406
4477
  // src/validation/validateXml.ts
4407
- var validateElement = (element, root, elementsToLink, linkElements, schemasToValidateAgainst) => {
4478
+ var ELEMENTS_PER_STEP = 64;
4479
+ var DEFAULT_TIME_BUDGET_MS = 8;
4480
+ var validateOwnElement = (element, {
4481
+ elementsToLink,
4482
+ linkElements,
4483
+ schemasToValidateAgainst,
4484
+ errors
4485
+ }) => {
4408
4486
  if (isIgnoredElement(element)) {
4409
- return [];
4487
+ return false;
4410
4488
  }
4411
- const errors = [];
4412
4489
  const schema = findSchemaByElement(element);
4413
- if (schema) {
4414
- if (schemasToValidateAgainst.includes(schema.code)) {
4415
- errors.push(
4416
- ...schema.code === BIBLIOMIXED_SCHEMA.code ? validateReference(element) : validateElementAgainstSchema(element, schema)
4417
- );
4418
- }
4419
- if (schema.tag === FOOTNOTE_SCHEMA.tag) {
4420
- errors.push(...validateFootnoteNumber(element));
4421
- }
4422
- if (schema.code === HEADING_SCHEMA.code) {
4423
- errors.push(...validateHierarchy(element, "heading"));
4424
- }
4425
- if (schema.code === BOX_SCHEMA.code) {
4426
- errors.push(...validateHierarchy(element, "box"));
4427
- }
4428
- if (schema.code === OLINK_SCHEMA.code) {
4429
- errors.push(...validateOlink(element));
4430
- }
4431
- errors.push(...validateLinkend(element, elementsToLink));
4432
- errors.push(...validateElementToLinkTo(element, linkElements));
4433
- if (isIgnoredValidation(schema.code)) {
4434
- return errors;
4435
- }
4490
+ if (!schema) {
4491
+ return true;
4436
4492
  }
4437
- for (const child of Array.from(element.children)) {
4493
+ if (schemasToValidateAgainst.includes(schema.code)) {
4438
4494
  errors.push(
4439
- ...validateElement(
4440
- child,
4441
- root,
4442
- elementsToLink,
4443
- linkElements,
4444
- schemasToValidateAgainst
4445
- )
4495
+ ...schema.code === BIBLIOMIXED_SCHEMA.code ? validateReference(element) : validateElementAgainstSchema(element, schema)
4446
4496
  );
4447
4497
  }
4448
- return errors;
4498
+ if (schema.tag === FOOTNOTE_SCHEMA.tag) {
4499
+ errors.push(...validateFootnoteNumber(element));
4500
+ }
4501
+ if (schema.code === HEADING_SCHEMA.code) {
4502
+ errors.push(...validateHierarchy(element, "heading"));
4503
+ }
4504
+ if (schema.code === BOX_SCHEMA.code) {
4505
+ errors.push(...validateHierarchy(element, "box"));
4506
+ }
4507
+ if (schema.code === OLINK_SCHEMA.code) {
4508
+ errors.push(...validateOlink(element));
4509
+ }
4510
+ errors.push(...validateLinkend(element, elementsToLink));
4511
+ errors.push(...validateElementToLinkTo(element, linkElements));
4512
+ return !isIgnoredValidation(schema.code);
4449
4513
  };
4450
- var validateXml = (xml, elementsToLink, linkElements, schemasToValidateAgainst) => {
4514
+ function* validateElements(xml, context) {
4515
+ const pending = Array.from(xml.children).reverse();
4516
+ let elementsSinceStep = 0;
4517
+ for (let element = pending.pop(); element; element = pending.pop()) {
4518
+ if (validateOwnElement(element, context)) {
4519
+ const { children } = element;
4520
+ for (let index = children.length - 1; index >= 0; index -= 1) {
4521
+ pending.push(children[index]);
4522
+ }
4523
+ }
4524
+ if (++elementsSinceStep === ELEMENTS_PER_STEP) {
4525
+ elementsSinceStep = 0;
4526
+ yield;
4527
+ }
4528
+ }
4529
+ }
4530
+ function* validateXmlSteps(xml, elementsToLink, linkElements, schemasToValidateAgainst) {
4451
4531
  const errors = [];
4452
4532
  errors.push(...validateConsecutiveFootnotes(xml));
4453
4533
  errors.push(...validateConsecutiveFootnoteCues(xml));
4454
- errors.push(...validatePossibleWeblinks(xml));
4455
- for (const child of Array.from(xml.children)) {
4456
- errors.push(
4457
- ...validateElement(
4458
- child,
4459
- xml,
4460
- elementsToLink,
4461
- linkElements,
4462
- schemasToValidateAgainst
4463
- )
4464
- );
4465
- }
4534
+ yield;
4535
+ errors.push(...yield* validatePossibleWeblinksSteps(xml));
4536
+ yield* validateElements(xml, {
4537
+ elementsToLink: new Set(elementsToLink),
4538
+ linkElements: new Set(linkElements),
4539
+ schemasToValidateAgainst,
4540
+ errors
4541
+ });
4466
4542
  return errors;
4543
+ }
4544
+ var validateXml = (xml, elementsToLink, linkElements, schemasToValidateAgainst) => runSteps(
4545
+ validateXmlSteps(
4546
+ xml,
4547
+ elementsToLink,
4548
+ linkElements,
4549
+ schemasToValidateAgainst
4550
+ )
4551
+ );
4552
+ var yieldToMainThread = () => {
4553
+ const scheduler = globalThis.scheduler;
4554
+ if (scheduler?.yield) return scheduler.yield();
4555
+ return new Promise((resolve) => setTimeout(resolve, 0));
4556
+ };
4557
+ var throwIfAborted = (signal) => {
4558
+ if (!signal?.aborted) return;
4559
+ throw signal.reason ?? new DOMException("Aborted", "AbortError");
4560
+ };
4561
+ var validateXmlAsync = async (xml, elementsToLink, linkElements, schemasToValidateAgainst, { signal, timeBudgetMs = DEFAULT_TIME_BUDGET_MS } = {}) => {
4562
+ const steps = validateXmlSteps(
4563
+ xml,
4564
+ elementsToLink,
4565
+ linkElements,
4566
+ schemasToValidateAgainst
4567
+ );
4568
+ let sliceStart = performance.now();
4569
+ for (; ; ) {
4570
+ throwIfAborted(signal);
4571
+ const step = steps.next();
4572
+ if (step.done) return step.value;
4573
+ if (performance.now() - sliceStart >= timeBudgetMs) {
4574
+ await yieldToMainThread();
4575
+ sliceStart = performance.now();
4576
+ }
4577
+ }
4467
4578
  };
4468
4579
 
4469
4580
  // src/helpers/getElementLinks.ts
@@ -4623,6 +4734,8 @@ var getAdjacentElement = ({
4623
4734
  validateLinkend,
4624
4735
  validateOlink,
4625
4736
  validatePossibleWeblinks,
4737
+ validatePossibleWeblinksSteps,
4626
4738
  validateReferenceStructure,
4627
- validateXml
4739
+ validateXml,
4740
+ validateXmlAsync
4628
4741
  });
package/dist/index.mjs CHANGED
@@ -2730,12 +2730,19 @@ var findAttributeDefinitionByName = (definitions, name) => {
2730
2730
  var findSchemaByCode = (code) => SCHEMAS[code];
2731
2731
 
2732
2732
  // src/schema/helpers/findSchemaByElement.ts
2733
- var findSchemaByElement = (element) => {
2734
- const schema = Object.values(SCHEMAS).find(
2735
- (schema2) => matchesSchemaTag(element, schema2.tag) && (!schema2.matchesElement || schema2.matchesElement(element))
2736
- );
2737
- return schema;
2733
+ var schemasByTag;
2734
+ var getSchemasByTag = () => {
2735
+ if (schemasByTag) return schemasByTag;
2736
+ schemasByTag = /* @__PURE__ */ new Map();
2737
+ for (const schema of Object.values(SCHEMAS)) {
2738
+ const tag = schema.tag.toUpperCase();
2739
+ const schemas = schemasByTag.get(tag);
2740
+ if (schemas) schemas.push(schema);
2741
+ else schemasByTag.set(tag, [schema]);
2742
+ }
2743
+ return schemasByTag;
2738
2744
  };
2745
+ var findSchemaByElement = (element) => getSchemasByTag().get(element.tagName.toUpperCase())?.find((schema) => !schema.matchesElement || schema.matchesElement(element));
2739
2746
 
2740
2747
  // src/schema/helpers/isAttributeRestricted.ts
2741
2748
  var isAttributeRestricted = (attr) => typeof attr !== "string";
@@ -3279,7 +3286,8 @@ var IGNORED_ELEMENT_SELECTORS = [
3279
3286
  "span.ref-drag",
3280
3287
  "span#dummySpan"
3281
3288
  ];
3282
- var isIgnoredElement = (element) => IGNORED_ELEMENT_SELECTORS.some((selector) => element.matches(selector));
3289
+ var IGNORED_ELEMENT_SELECTOR = IGNORED_ELEMENT_SELECTORS.join(", ");
3290
+ var isIgnoredElement = (element) => element.tagName.toUpperCase() === "SPAN" && element.matches(IGNORED_ELEMENT_SELECTOR);
3283
3291
 
3284
3292
  // src/validation/helpers/isIgnoredValidation.ts
3285
3293
  var IGNORED_VALIDATION_ELEMENT_SELECTORS = [
@@ -3438,10 +3446,11 @@ var validateConsecutiveFootnoteCues = (root) => {
3438
3446
  return errors;
3439
3447
  };
3440
3448
 
3449
+ // src/validation/helpers/hasLinkId.ts
3450
+ var hasLinkId = (ids, id) => "has" in ids ? ids.has(id) : ids.includes(id);
3451
+
3441
3452
  // src/validation/validateLinkend.ts
3442
- var findElementLinkedTo = (linkend, elementsToLink) => {
3443
- return elementsToLink.includes(linkend);
3444
- };
3453
+ var findElementLinkedTo = (linkend, elementsToLink) => hasLinkId(elementsToLink, linkend);
3445
3454
  var LINK_TO_CROSS_CHECK = [
3446
3455
  TABLE_LINK_ROLE,
3447
3456
  FIGURE_LINK_ROLE,
@@ -3662,9 +3671,7 @@ var getExtraInstruction = (schema, childSchema) => {
3662
3671
  };
3663
3672
 
3664
3673
  // src/validation/validateElementToLinkTo.ts
3665
- var findElementLinkedTo2 = (xmlid, linkElements) => {
3666
- return linkElements.includes(xmlid);
3667
- };
3674
+ var findElementLinkedTo2 = (xmlid, linkElements) => hasLinkId(linkElements, xmlid);
3668
3675
  var isFootnote = (element) => {
3669
3676
  return matchesSchemaTag(element, FOOTNOTE_SCHEMA.tag);
3670
3677
  };
@@ -3734,6 +3741,16 @@ var validateElementToLinkTo = (element, linkElements) => {
3734
3741
 
3735
3742
  // src/validation/validateWeblink.ts
3736
3743
  import Mark from "mark.js";
3744
+
3745
+ // src/validation/helpers/steps.ts
3746
+ var runSteps = (steps) => {
3747
+ for (; ; ) {
3748
+ const step = steps.next();
3749
+ if (step.done) return step.value;
3750
+ }
3751
+ };
3752
+
3753
+ // src/validation/validateWeblink.ts
3737
3754
  var EDGE_PUNCTUATION = `:;.,\\-\u2013\u2014\u2012_(){}[\\]!'"+=\xAB\xBB\u2039\u203A\u201C\u201D\u2018\u2019<>`;
3738
3755
  var WEB_URI_PATTERN = `(?:(?:https?):\\/\\/|www\\.)(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\\.)+[a-zA-Z]{2,}(?::\\d{1,5})?(?:\\/(?:[^\\s]*[^\\s${EDGE_PUNCTUATION}])?)?`;
3739
3756
  var WEB_URI_IN_TEXT_REGEX = new RegExp(WEB_URI_PATTERN, "gi");
@@ -3814,11 +3831,62 @@ var markPossibleWeblinks = (editor) => {
3814
3831
  });
3815
3832
  return possibleWeblinks;
3816
3833
  };
3817
- var validatePossibleWeblinks = (root) => {
3834
+ var TEXT_NODES_PER_STEP = 256;
3835
+ var MARK_EXCLUDED_PARENT_SELECTOR = [
3836
+ ...POSSIBLE_WEBLINK_EXCLUDE_SELECTORS,
3837
+ "script",
3838
+ "style",
3839
+ "title",
3840
+ "head",
3841
+ "html"
3842
+ ].join(", ");
3843
+ var createWeblinkMatchElement = (ownerDocument, text, storeId) => {
3844
+ const element = ownerDocument.createElement("mark");
3845
+ element.setAttribute("data-markjs", "true");
3846
+ element.setAttribute("class", POSSIBLE_WEBLINK_CLASS);
3847
+ element.setAttribute(STORE_ATTRIBUTE, storeId);
3848
+ element.textContent = text;
3849
+ return element;
3850
+ };
3851
+ function* findPossibleWeblinks(root) {
3852
+ const possibleWeblinks = /* @__PURE__ */ new Map();
3853
+ const ownerDocument = root.ownerDocument;
3854
+ const walker = ownerDocument.createTreeWalker(root, NodeFilter.SHOW_TEXT);
3855
+ const regex = new RegExp(WEB_URI_PATTERN, "gi");
3856
+ let nodesSinceStep = 0;
3857
+ for (let node = walker.nextNode(); node; node = walker.nextNode()) {
3858
+ if (++nodesSinceStep === TEXT_NODES_PER_STEP) {
3859
+ nodesSinceStep = 0;
3860
+ yield;
3861
+ }
3862
+ const text = node.textContent ?? "";
3863
+ if (!text) continue;
3864
+ const parent = node.parentNode;
3865
+ if (parent instanceof Element && parent.matches(MARK_EXCLUDED_PARENT_SELECTOR)) {
3866
+ continue;
3867
+ }
3868
+ regex.lastIndex = 0;
3869
+ for (let match = regex.exec(text); match; match = regex.exec(text)) {
3870
+ const storeId = getWeblinkMatchId(possibleWeblinks.size);
3871
+ possibleWeblinks.set(storeId, {
3872
+ weblink: match[0],
3873
+ elements: [createWeblinkMatchElement(ownerDocument, match[0], storeId)]
3874
+ });
3875
+ }
3876
+ }
3877
+ return possibleWeblinks;
3878
+ }
3879
+ var hasWeblinkMatches = (root) => root.getElementsByClassName(POSSIBLE_WEBLINK_CLASS).length > 0;
3880
+ function* validatePossibleWeblinksSteps(root) {
3818
3881
  const issues = [];
3819
- const cloneRoot = root.cloneNode(true);
3820
- clearWeblinkMatches(cloneRoot);
3821
- const possibleWeblinks = markPossibleWeblinks(cloneRoot);
3882
+ let possibleWeblinks;
3883
+ if (hasWeblinkMatches(root)) {
3884
+ const cloneRoot = root.cloneNode(true);
3885
+ clearWeblinkMatches(cloneRoot);
3886
+ possibleWeblinks = markPossibleWeblinks(cloneRoot);
3887
+ } else {
3888
+ possibleWeblinks = yield* findPossibleWeblinks(root);
3889
+ }
3822
3890
  for (const [storeId, { elements }] of Array.from(possibleWeblinks)) {
3823
3891
  issues.push(
3824
3892
  createValidationIssue(null, {
@@ -3832,7 +3900,8 @@ var validatePossibleWeblinks = (root) => {
3832
3900
  );
3833
3901
  }
3834
3902
  return issues;
3835
- };
3903
+ }
3904
+ var validatePossibleWeblinks = (root) => runSteps(validatePossibleWeblinksSteps(root));
3836
3905
  var clearWeblinkMatches = (editor) => {
3837
3906
  const instance = new Mark(editor);
3838
3907
  instance.unmark({
@@ -4289,66 +4358,106 @@ var validateReference = (reference) => {
4289
4358
  };
4290
4359
 
4291
4360
  // src/validation/validateXml.ts
4292
- var validateElement = (element, root, elementsToLink, linkElements, schemasToValidateAgainst) => {
4361
+ var ELEMENTS_PER_STEP = 64;
4362
+ var DEFAULT_TIME_BUDGET_MS = 8;
4363
+ var validateOwnElement = (element, {
4364
+ elementsToLink,
4365
+ linkElements,
4366
+ schemasToValidateAgainst,
4367
+ errors
4368
+ }) => {
4293
4369
  if (isIgnoredElement(element)) {
4294
- return [];
4370
+ return false;
4295
4371
  }
4296
- const errors = [];
4297
4372
  const schema = findSchemaByElement(element);
4298
- if (schema) {
4299
- if (schemasToValidateAgainst.includes(schema.code)) {
4300
- errors.push(
4301
- ...schema.code === BIBLIOMIXED_SCHEMA.code ? validateReference(element) : validateElementAgainstSchema(element, schema)
4302
- );
4303
- }
4304
- if (schema.tag === FOOTNOTE_SCHEMA.tag) {
4305
- errors.push(...validateFootnoteNumber(element));
4306
- }
4307
- if (schema.code === HEADING_SCHEMA.code) {
4308
- errors.push(...validateHierarchy(element, "heading"));
4309
- }
4310
- if (schema.code === BOX_SCHEMA.code) {
4311
- errors.push(...validateHierarchy(element, "box"));
4312
- }
4313
- if (schema.code === OLINK_SCHEMA.code) {
4314
- errors.push(...validateOlink(element));
4315
- }
4316
- errors.push(...validateLinkend(element, elementsToLink));
4317
- errors.push(...validateElementToLinkTo(element, linkElements));
4318
- if (isIgnoredValidation(schema.code)) {
4319
- return errors;
4320
- }
4373
+ if (!schema) {
4374
+ return true;
4321
4375
  }
4322
- for (const child of Array.from(element.children)) {
4376
+ if (schemasToValidateAgainst.includes(schema.code)) {
4323
4377
  errors.push(
4324
- ...validateElement(
4325
- child,
4326
- root,
4327
- elementsToLink,
4328
- linkElements,
4329
- schemasToValidateAgainst
4330
- )
4378
+ ...schema.code === BIBLIOMIXED_SCHEMA.code ? validateReference(element) : validateElementAgainstSchema(element, schema)
4331
4379
  );
4332
4380
  }
4333
- return errors;
4381
+ if (schema.tag === FOOTNOTE_SCHEMA.tag) {
4382
+ errors.push(...validateFootnoteNumber(element));
4383
+ }
4384
+ if (schema.code === HEADING_SCHEMA.code) {
4385
+ errors.push(...validateHierarchy(element, "heading"));
4386
+ }
4387
+ if (schema.code === BOX_SCHEMA.code) {
4388
+ errors.push(...validateHierarchy(element, "box"));
4389
+ }
4390
+ if (schema.code === OLINK_SCHEMA.code) {
4391
+ errors.push(...validateOlink(element));
4392
+ }
4393
+ errors.push(...validateLinkend(element, elementsToLink));
4394
+ errors.push(...validateElementToLinkTo(element, linkElements));
4395
+ return !isIgnoredValidation(schema.code);
4334
4396
  };
4335
- var validateXml = (xml, elementsToLink, linkElements, schemasToValidateAgainst) => {
4397
+ function* validateElements(xml, context) {
4398
+ const pending = Array.from(xml.children).reverse();
4399
+ let elementsSinceStep = 0;
4400
+ for (let element = pending.pop(); element; element = pending.pop()) {
4401
+ if (validateOwnElement(element, context)) {
4402
+ const { children } = element;
4403
+ for (let index = children.length - 1; index >= 0; index -= 1) {
4404
+ pending.push(children[index]);
4405
+ }
4406
+ }
4407
+ if (++elementsSinceStep === ELEMENTS_PER_STEP) {
4408
+ elementsSinceStep = 0;
4409
+ yield;
4410
+ }
4411
+ }
4412
+ }
4413
+ function* validateXmlSteps(xml, elementsToLink, linkElements, schemasToValidateAgainst) {
4336
4414
  const errors = [];
4337
4415
  errors.push(...validateConsecutiveFootnotes(xml));
4338
4416
  errors.push(...validateConsecutiveFootnoteCues(xml));
4339
- errors.push(...validatePossibleWeblinks(xml));
4340
- for (const child of Array.from(xml.children)) {
4341
- errors.push(
4342
- ...validateElement(
4343
- child,
4344
- xml,
4345
- elementsToLink,
4346
- linkElements,
4347
- schemasToValidateAgainst
4348
- )
4349
- );
4350
- }
4417
+ yield;
4418
+ errors.push(...yield* validatePossibleWeblinksSteps(xml));
4419
+ yield* validateElements(xml, {
4420
+ elementsToLink: new Set(elementsToLink),
4421
+ linkElements: new Set(linkElements),
4422
+ schemasToValidateAgainst,
4423
+ errors
4424
+ });
4351
4425
  return errors;
4426
+ }
4427
+ var validateXml = (xml, elementsToLink, linkElements, schemasToValidateAgainst) => runSteps(
4428
+ validateXmlSteps(
4429
+ xml,
4430
+ elementsToLink,
4431
+ linkElements,
4432
+ schemasToValidateAgainst
4433
+ )
4434
+ );
4435
+ var yieldToMainThread = () => {
4436
+ const scheduler = globalThis.scheduler;
4437
+ if (scheduler?.yield) return scheduler.yield();
4438
+ return new Promise((resolve) => setTimeout(resolve, 0));
4439
+ };
4440
+ var throwIfAborted = (signal) => {
4441
+ if (!signal?.aborted) return;
4442
+ throw signal.reason ?? new DOMException("Aborted", "AbortError");
4443
+ };
4444
+ var validateXmlAsync = async (xml, elementsToLink, linkElements, schemasToValidateAgainst, { signal, timeBudgetMs = DEFAULT_TIME_BUDGET_MS } = {}) => {
4445
+ const steps = validateXmlSteps(
4446
+ xml,
4447
+ elementsToLink,
4448
+ linkElements,
4449
+ schemasToValidateAgainst
4450
+ );
4451
+ let sliceStart = performance.now();
4452
+ for (; ; ) {
4453
+ throwIfAborted(signal);
4454
+ const step = steps.next();
4455
+ if (step.done) return step.value;
4456
+ if (performance.now() - sliceStart >= timeBudgetMs) {
4457
+ await yieldToMainThread();
4458
+ sliceStart = performance.now();
4459
+ }
4460
+ }
4352
4461
  };
4353
4462
 
4354
4463
  // src/helpers/getElementLinks.ts
@@ -4507,6 +4616,8 @@ export {
4507
4616
  validateLinkend,
4508
4617
  validateOlink,
4509
4618
  validatePossibleWeblinks,
4619
+ validatePossibleWeblinksSteps,
4510
4620
  validateReferenceStructure,
4511
- validateXml
4621
+ validateXml,
4622
+ validateXmlAsync
4512
4623
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pro-editor-schema",
3
- "version": "1.5.5-7",
3
+ "version": "1.5.5-8",
4
4
  "description": "Pro Editor XML schemas",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",