pro-editor-schema 0.1.200 → 0.2.1

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
@@ -7,41 +7,64 @@ TypeScript library that defines XML/HTML schema rules used by the editor and pro
7
7
  - Schema definitions for supported tags and schema codes.
8
8
  - Shared schema constants and attribute helpers.
9
9
  - Runtime schema lookup utilities.
10
- - XML validation utilities (`validateXml`, attribute/child validators, footnote and link validators).
10
+ - XML validation utilities (`validateXml` and related validators).
11
11
  - Type-safe element factory (`createElement`) based on schema definitions.
12
12
 
13
- ## Install and build
13
+ ## Install
14
14
 
15
15
  ```bash
16
16
  npm install
17
- npm run typecheck
18
- npm run test
19
- npm run build
20
17
  ```
21
18
 
19
+ ## Scripts
20
+
21
+ | Command | Description |
22
+ | --- | --- |
23
+ | `npm run dev` | Build in watch mode |
24
+ | `npm run build` | Build CJS, ESM, and type declarations |
25
+ | `npm run typecheck` | Run TypeScript without emitting files |
26
+ | `npm run lint` | Run ESLint on `src/` |
27
+ | `npm run test` | Run tests once with Vitest |
28
+ | `npm run test:watch` | Run tests in watch mode |
29
+ | `npm run check` | Run the full verification pipeline (`npm ci`, `typecheck`, `lint`, `test`, `build`) |
30
+
31
+ For local development, `npm run check` is the fastest way to verify everything before pushing.
32
+
33
+ ## CI
34
+
35
+ GitHub Actions runs on push and pull requests to `main` / `master`:
36
+
37
+ - **Typecheck** — `npm run typecheck`
38
+ - **Lint** — `npm run lint`
39
+ - **Test** — `npm test`
40
+
41
+ Workflow file: `.github/workflows/ci.yml`
42
+
22
43
  ## Project structure
23
44
 
24
- - `src/index.ts`: public exports for schemas, types, shared constants, and validation.
25
- - `src/schema/schema.types.ts`: core schema and validation-related types.
26
- - `src/schema/schemas.ts`: central `SCHEMAS` map (`code -> schema`).
27
- - `src/schema/elementSchemas/elements`: element schema definitions and `ELEMENT_SCHEMAS`.
28
- - `src/schema/elementSchemas/atoms`: atom schema definitions and `ATOM_SCHEMAS`.
29
- - `src/schema/elementSchemas/inlineElements`: inline schema definitions and `INLINE_ELEMENT_SCHEMAS`.
30
- - `src/schema/shared`: shared attribute and ignored-node constants.
31
- - `src/schema/utils`: schema lookup and element creation helpers.
32
- - `src/validation`: document validation entrypoint and validation modules.
45
+ - `src/index.ts` — public exports for schemas, types, shared constants, and validation.
46
+ - `src/schema/schema.types.ts` — core schema and validation-related types.
47
+ - `src/schema/schemas.ts` — central `SCHEMAS` map (`code -> schema`).
48
+ - `src/schema/elementSchemas/elements` — element schema definitions and `ELEMENT_SCHEMAS`.
49
+ - `src/schema/elementSchemas/atoms` — atom schema definitions and `ATOM_SCHEMAS`.
50
+ - `src/schema/elementSchemas/inlineElements` — inline schema definitions and `INLINE_ELEMENT_SCHEMAS`.
51
+ - `src/schema/shared` — shared attribute and ignored-node constants.
52
+ - `src/schema/utils` — schema lookup, element creation, and label helpers.
53
+ - `src/validation` — document validation entrypoint and validation modules.
54
+ - `src/**/*.test.ts` — Vitest test files (run with `happy-dom`).
33
55
 
34
56
  ## Schema model
35
57
 
36
58
  Each schema defines:
37
59
 
38
- - `code`: internal schema identifier.
39
- - `kind`: schema category (`element`, `atom`, `inline`).
40
- - `tag`: uppercase XML tag name used in documents.
41
- - `requiredAttributes`: required attributes (free or restricted values).
42
- - `optionalAttributes`: optional attributes (free or restricted values).
43
- - `defaultChildTagName`: preferred child tag (optional, used by some block-like schemas).
44
- - `allowedChildTagNames`: allowed child tags and whether each child is required.
60
+ - `code` — internal schema identifier.
61
+ - `kind` — schema category (`element`, `atom`, `inline`).
62
+ - `tag` — uppercase XML tag name used in documents.
63
+ - `requiredAttributes` — required attributes (free or restricted values).
64
+ - `optionalAttributes` — optional attributes (free or restricted values).
65
+ - `defaultChildCode` — preferred child tag (optional, used by some block-like schemas).
66
+ - `allowedChildrenCodes` — allowed child tags and whether each child is required.
67
+ - `matchesElement` — optional predicate to resolve elements that share the same tag (for example `SPAN`, `SECTION1`).
45
68
 
46
69
  Schemas are combined in `SCHEMAS` from:
47
70
 
@@ -49,15 +72,29 @@ Schemas are combined in `SCHEMAS` from:
49
72
  - `ATOM_SCHEMAS`
50
73
  - `INLINE_ELEMENT_SCHEMAS`
51
74
 
75
+ ### Resolving schemas from DOM elements
76
+
77
+ `findSchemaByElement` matches by tag name. When multiple schemas share a tag, the first schema with a matching `matchesElement` predicate wins.
78
+
79
+ Examples:
80
+
81
+ - `SPAN` with class `ins` → `insertion`
82
+ - `SPAN` with class `del` → `deletion`
83
+ - `SPAN` with class `cmtQurySection` → `commentQuerySection`
84
+ - `SECTION1` with `role="H1"` → `heading`
85
+ - `SECTION1` with `role="ch"` or a `FOOTNOTE` child → `footnoteSection`
86
+
87
+ When adding a new schema that reuses an existing tag, define `matchesElement` on every schema that shares that tag.
88
+
52
89
  ## Public API highlights
53
90
 
54
91
  From `src/index.ts`, consumers can import:
55
92
 
56
- - Types: `Schema`, `SchemaCode`, `ElementSchemaCode`, `AtomSchemaCode`, `InlineSchemaCode`
57
- - Maps: `SCHEMAS`, `ELEMENT_SCHEMAS`, `ATOM_SCHEMAS`, `INLINE_ELEMENT_SCHEMAS`
58
- - Utilities: `findSchemaByCode`, `findSchemaByElement`, `createElement`
59
- - Validators: `validateXml`, `validateFootnotes`, `validateLinkend`, `validateAttributes`, `validateChildren`
60
- - Shared constants: ignored elements/attributes and global attribute constants
93
+ - **Types:** `Schema`, `SchemaCode`, `ElementSchemaCode`, `AtomSchemaCode`, `InlineSchemaCode`, `ValidationError`
94
+ - **Maps:** `SCHEMAS`, `ELEMENT_SCHEMAS`, `ATOM_SCHEMAS`, `INLINE_ELEMENT_SCHEMAS`
95
+ - **Utilities:** `findSchemaByCode`, `findSchemaByElement`, `createElement`, `ElementLabel`
96
+ - **Validation:** `validateXml`
97
+ - **Shared constants:** ignored elements/attributes, global attribute constants, dependents forbidden elements
61
98
 
62
99
  ## Usage examples
63
100
 
@@ -79,7 +116,6 @@ const para = createElement({
79
116
  attributes: {
80
117
  store: "body",
81
118
  "ele-id": "p-1",
82
- role: "body",
83
119
  },
84
120
  });
85
121
  ```
@@ -89,23 +125,41 @@ const para = createElement({
89
125
  ```ts
90
126
  import { validateXml } from "pro-editor-schema";
91
127
 
92
- const errors = validateXml(rootElement, linkedIds, true);
128
+ const errors = validateXml(
129
+ rootElement,
130
+ elementsToLink,
131
+ linkElements,
132
+ ["para", "heading"],
133
+ );
93
134
  ```
94
135
 
95
- `validateXml` performs:
136
+ `validateXml` parameters:
137
+
138
+ - `xml` — root element to validate.
139
+ - `elementsToLink` — IDs available as `linkend` targets.
140
+ - `linkElements` — IDs cited by links (used for reference validation).
141
+ - `schemasToValidateAgainst` — schema codes to run attribute/child validation against.
142
+
143
+ `validateXml` always runs:
144
+
145
+ - Consecutive footnote number checks
146
+ - Consecutive footnote cue checks
147
+
148
+ It also runs per-element checks for:
149
+
150
+ - Link targets (`linkend`) on linkable elements
151
+ - Uncited bibliographic references
152
+ - Unreferenced footnotes
96
153
 
97
- - Footnote sequence checks
98
- - Footnote cue sequence checks
99
- - Link target checks (`linkend`)
100
- - Optional schema-based attribute and child validation when enabled
154
+ Schema-based attribute and child validation only runs for codes listed in `schemasToValidateAgainst`.
101
155
 
102
156
  ## How validation works
103
157
 
104
158
  - `validateXml` traverses the XML tree and delegates checks per element.
105
- - `findSchemaByElement` resolves schema from classes, role, and tag fallback.
106
- - `validateAttributes` ensures required attributes exist and restricted values are valid.
159
+ - `findSchemaByElement` resolves schema from tag and optional `matchesElement`.
160
+ - `validateAttributes` validates restricted attribute values.
107
161
  - `validateChildren` ensures child tags are allowed and required children exist.
108
- - `validateFootnotes` and `validateLinkend` provide cross-reference integrity checks.
162
+ - `validateFootnotes`, `validateLinkend`, and `validateReferenceCited` provide cross-reference integrity checks.
109
163
 
110
164
  Validation results use `ValidationError`:
111
165
 
@@ -118,23 +172,23 @@ Validation results use `ValidationError`:
118
172
 
119
173
  ## Adding or updating a schema
120
174
 
121
- 1. Create or update schema file in one of:
175
+ 1. Create or update a schema file in one of:
122
176
  - `src/schema/elementSchemas/elements`
123
177
  - `src/schema/elementSchemas/atoms`
124
178
  - `src/schema/elementSchemas/inlineElements`
125
- 2. Define schema with `createSchema(...)`.
126
- 3. Add it to the corresponding registry:
179
+ 2. Define the schema with `createSchema(...)`.
180
+ 3. If the tag is already used by another schema, add a `matchesElement` predicate.
181
+ 4. Register it in the corresponding map:
127
182
  - `ELEMENT_SCHEMAS`
128
183
  - `ATOM_SCHEMAS`
129
184
  - `INLINE_ELEMENT_SCHEMAS`
130
- 4. Ensure the schema code exists in the matching union type in `schema.types.ts`.
131
- 5. If needed, export constants/types from folder `index.ts` and `src/index.ts`.
132
- 6. Run:
133
- - `npm run typecheck`
134
- - `npm run test`
185
+ 5. Ensure the schema code exists in the matching union type in `schema.types.ts`.
186
+ 6. Add or update tests for any new validation or utility behavior.
187
+ 7. Run `npm run check`.
135
188
 
136
189
  ## Notes
137
190
 
138
191
  - Tag matching is done in uppercase in schema definitions and validation.
139
192
  - Some schema codes represent block/group-like structures while still being typed as `kind: "element"` in the current model.
140
193
  - Ignored attributes and ignored UI helper elements are excluded from strict validation by design.
194
+ - Track-changes atoms (`insertion`, `deletion`) are unwrapped when validating required children.
package/dist/index.d.mts CHANGED
@@ -17,21 +17,21 @@ type Schema<TRequiredAttributes = readonly AttributeDefinition[], TOptionalAttri
17
17
  requiredAttributes: TRequiredAttributes;
18
18
  optionalAttributes: TOptionalAttributes;
19
19
  defaultChildCode?: SchemaCode;
20
- allowedChildrenCodes: ReadonlySet<AllowedChildren>;
20
+ allowedChildrenCodes: AllowedChildren[];
21
+ matchesElement?: (element: Element) => boolean;
21
22
  };
22
23
  type SchemaCode = ElementSchemaCode | InlineSchemaCode | AtomSchemaCode;
23
24
  type ElementSchemaCode = "referenceBibliomixed" | "referenceBibliography" | "bibliodiv" | "degrees" | "email" | "equation" | "fignote" | "figsource" | "firstname" | "footnote" | "footnoteno" | "honorific" | "img" | "informalfigure" | "keyword" | "label1" | "li" | "line" | "lrh" | "math" | "mathphrase" | "merror" | "mfrac" | "mi" | "mmultiscripts" | "mn" | "mo" | "mover" | "mpadded" | "mphantom" | "mprescripts" | "mroot" | "mrow" | "ms" | "mspace" | "msqrt" | "mstyle" | "msub" | "msubsup" | "msup" | "mtable" | "mtd" | "mtext" | "mtr" | "munder" | "munderover" | "olink" | "org" | "orgname" | "para" | "punc" | "rrh" | "source1" | "speaker" | "subtitle1" | "surname" | "tag" | "tblfn" | "tblsource" | "thead" | "title1" | "token" | "tp" | "abstract1" | "affiliation" | "alttext" | "appendix" | "author" | "authorgroup" | "blockquote" | "caption1" | "chapter" | "dialogue" | "epigraph" | "figure1" | "footnoteSection" | "heading" | "imageobject" | "info1" | "keywordset" | "mediaobject" | "ol" | "personname" | "poem" | "sidebar" | "table" | "table1" | "tbody" | "td" | "tgroup" | "tr" | "ul";
24
25
  type AtomSchemaCode = "indexterm" | "primary" | "secondary" | "tertiary" | "quaternary" | "bookmark" | "see" | "commentQuerySection" | "comment" | "content" | "query" | "reply" | "deletion" | "emphasis" | "highlightmark" | "i" | "insertion" | "sc" | "strike" | "sub" | "sup" | "u" | "b" | "referenceMark";
25
26
  type InlineSchemaCode = "footnote1" | "inlineequation" | "link" | "uri" | "phrase";
26
- declare const ElementLabel: Record<SchemaCode, string>;
27
27
 
28
28
  declare const SCHEMAS: Record<SchemaCode, Schema>;
29
29
 
30
+ declare const isTrackChangesSchema: (schema: Schema) => boolean;
30
31
  declare const isIgnoredAttribute: (attribute: string) => boolean;
31
32
  declare const isIgnoredElement: (element: Element) => boolean;
32
33
  declare const isIgnoredValidation: (code: SchemaCode) => boolean;
33
- declare const findSchemaByCode: (code: SchemaCode) => Schema | undefined;
34
- declare const findSchemaByTagName: (tagName: string) => Schema[];
34
+ declare const findSchemaByCode: (code: SchemaCode) => Schema;
35
35
  declare const findSchemaByElement: (element: Element) => Schema | undefined;
36
36
  declare const isAttributeRestricted: (attr: AttributeDefinition) => attr is RestrictedAttribute;
37
37
  declare const getAttributeName: (attribute: AttributeDefinition) => string;
@@ -49,12 +49,20 @@ type CreateElementParams<TSchema extends Schema> = {
49
49
  };
50
50
  declare const createElement: <TSchema extends Schema>({ schemaCode, attributes, }: CreateElementParams<TSchema>) => HTMLElement;
51
51
 
52
+ declare const ElementLabel: Record<SchemaCode, string>;
53
+
52
54
  declare const IGNORED_ELEMENT_SELECTORS: string[];
53
55
  declare const IGNORED_VALIDATION_ELEMENT_SELECTORS: SchemaCode[];
54
56
 
55
57
  declare const IGNORED_ATTRIBUTES: string[];
56
58
  declare const IGNORED_ATTRIBUTES_STARTS_WITH: string[];
57
59
 
60
+ type DependentForbiddenElement = {
61
+ code: SchemaCode;
62
+ dependentElements: SchemaCode[];
63
+ };
64
+ declare const DEPENDENT_FORBIDDEN_ELEMENTS: DependentForbiddenElement[];
65
+
58
66
  declare const ALIGNMENT_ATTRIBUTE = "data-alignment";
59
67
  declare const LEFT_INDENT_ATTRIBUTE = "data-left-indent";
60
68
  declare const RIGHT_INDENT_ATTRIBUTE = "data-right-indent";
@@ -105,6 +113,6 @@ type ValidationError = {
105
113
  * * ValidateConsecutiveFootnotes and ValidateConsecutiveFootnoteCues are default validations.
106
114
  * @returns The validation errors.
107
115
  */
108
- declare const validateXml: (xml: Element, elementsToLink: readonly string[], linkElements: readonly string[], schemasToValidateAgainst: SchemaCode[]) => ValidationError[];
116
+ declare const validateXml: (xml: Element, elementsToLink: string[], linkElements: string[], schemasToValidateAgainst: SchemaCode[]) => ValidationError[];
109
117
 
110
- export { ALIGNMENT_ATTRIBUTE, ATOM_SCHEMAS, type AllowedChildren, type AtomSchemaCode, type AttributeDefinition, ELEMENT_SCHEMAS, ELE_ID_ATTRIBUTE, ElementLabel, type ElementSchemaCode, FIGURE_PLACEMENT_ATTRIBUTE, type FreeAttribute, ID_ATTRS, IGNORED_ATTRIBUTES, IGNORED_ATTRIBUTES_STARTS_WITH, IGNORED_ELEMENT_SELECTORS, IGNORED_VALIDATION_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, RIGHT_INDENT_ATTRIBUTE, type RestrictedAttribute, SCHEMAS, SPACE_ABOVE_ATTRIBUTE, SPACE_BELOW_ATTRIBUTE, SPECIAL_INDENT_ATTRIBUTE, STORE_ATTRIBUTE, STYLE_NAME_ATTRIBUTE, type Schema, type SchemaCode, type SchemaKind, TRACK_CHANGES_ATTRS, TRACK_CHANGES_ATTR_DATA_DATE, TRACK_CHANGES_ATTR_DATA_TIME, TRACK_CHANGES_ATTR_DATA_USERID, TRACK_CHANGES_ATTR_DATA_USERNAME, type ValidationError, type ValidationErrorKind, type ValidationErrorLevel, createElement, findAttributeDefinitionByName, findSchemaByCode, findSchemaByElement, findSchemaByTagName, getAttributeName, isAttributeRestricted, isIgnoredAttribute, isIgnoredElement, isIgnoredValidation, validateXml };
118
+ export { ALIGNMENT_ATTRIBUTE, ATOM_SCHEMAS, type AllowedChildren, type AtomSchemaCode, type AttributeDefinition, 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, IGNORED_VALIDATION_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, RIGHT_INDENT_ATTRIBUTE, type RestrictedAttribute, SCHEMAS, SPACE_ABOVE_ATTRIBUTE, SPACE_BELOW_ATTRIBUTE, SPECIAL_INDENT_ATTRIBUTE, STORE_ATTRIBUTE, STYLE_NAME_ATTRIBUTE, type Schema, type SchemaCode, type SchemaKind, TRACK_CHANGES_ATTRS, TRACK_CHANGES_ATTR_DATA_DATE, TRACK_CHANGES_ATTR_DATA_TIME, TRACK_CHANGES_ATTR_DATA_USERID, TRACK_CHANGES_ATTR_DATA_USERNAME, type ValidationError, type ValidationErrorKind, type ValidationErrorLevel, createElement, findAttributeDefinitionByName, findSchemaByCode, findSchemaByElement, getAttributeName, isAttributeRestricted, isIgnoredAttribute, isIgnoredElement, isIgnoredValidation, isTrackChangesSchema, validateXml };
package/dist/index.d.ts CHANGED
@@ -17,21 +17,21 @@ type Schema<TRequiredAttributes = readonly AttributeDefinition[], TOptionalAttri
17
17
  requiredAttributes: TRequiredAttributes;
18
18
  optionalAttributes: TOptionalAttributes;
19
19
  defaultChildCode?: SchemaCode;
20
- allowedChildrenCodes: ReadonlySet<AllowedChildren>;
20
+ allowedChildrenCodes: AllowedChildren[];
21
+ matchesElement?: (element: Element) => boolean;
21
22
  };
22
23
  type SchemaCode = ElementSchemaCode | InlineSchemaCode | AtomSchemaCode;
23
24
  type ElementSchemaCode = "referenceBibliomixed" | "referenceBibliography" | "bibliodiv" | "degrees" | "email" | "equation" | "fignote" | "figsource" | "firstname" | "footnote" | "footnoteno" | "honorific" | "img" | "informalfigure" | "keyword" | "label1" | "li" | "line" | "lrh" | "math" | "mathphrase" | "merror" | "mfrac" | "mi" | "mmultiscripts" | "mn" | "mo" | "mover" | "mpadded" | "mphantom" | "mprescripts" | "mroot" | "mrow" | "ms" | "mspace" | "msqrt" | "mstyle" | "msub" | "msubsup" | "msup" | "mtable" | "mtd" | "mtext" | "mtr" | "munder" | "munderover" | "olink" | "org" | "orgname" | "para" | "punc" | "rrh" | "source1" | "speaker" | "subtitle1" | "surname" | "tag" | "tblfn" | "tblsource" | "thead" | "title1" | "token" | "tp" | "abstract1" | "affiliation" | "alttext" | "appendix" | "author" | "authorgroup" | "blockquote" | "caption1" | "chapter" | "dialogue" | "epigraph" | "figure1" | "footnoteSection" | "heading" | "imageobject" | "info1" | "keywordset" | "mediaobject" | "ol" | "personname" | "poem" | "sidebar" | "table" | "table1" | "tbody" | "td" | "tgroup" | "tr" | "ul";
24
25
  type AtomSchemaCode = "indexterm" | "primary" | "secondary" | "tertiary" | "quaternary" | "bookmark" | "see" | "commentQuerySection" | "comment" | "content" | "query" | "reply" | "deletion" | "emphasis" | "highlightmark" | "i" | "insertion" | "sc" | "strike" | "sub" | "sup" | "u" | "b" | "referenceMark";
25
26
  type InlineSchemaCode = "footnote1" | "inlineequation" | "link" | "uri" | "phrase";
26
- declare const ElementLabel: Record<SchemaCode, string>;
27
27
 
28
28
  declare const SCHEMAS: Record<SchemaCode, Schema>;
29
29
 
30
+ declare const isTrackChangesSchema: (schema: Schema) => boolean;
30
31
  declare const isIgnoredAttribute: (attribute: string) => boolean;
31
32
  declare const isIgnoredElement: (element: Element) => boolean;
32
33
  declare const isIgnoredValidation: (code: SchemaCode) => boolean;
33
- declare const findSchemaByCode: (code: SchemaCode) => Schema | undefined;
34
- declare const findSchemaByTagName: (tagName: string) => Schema[];
34
+ declare const findSchemaByCode: (code: SchemaCode) => Schema;
35
35
  declare const findSchemaByElement: (element: Element) => Schema | undefined;
36
36
  declare const isAttributeRestricted: (attr: AttributeDefinition) => attr is RestrictedAttribute;
37
37
  declare const getAttributeName: (attribute: AttributeDefinition) => string;
@@ -49,12 +49,20 @@ type CreateElementParams<TSchema extends Schema> = {
49
49
  };
50
50
  declare const createElement: <TSchema extends Schema>({ schemaCode, attributes, }: CreateElementParams<TSchema>) => HTMLElement;
51
51
 
52
+ declare const ElementLabel: Record<SchemaCode, string>;
53
+
52
54
  declare const IGNORED_ELEMENT_SELECTORS: string[];
53
55
  declare const IGNORED_VALIDATION_ELEMENT_SELECTORS: SchemaCode[];
54
56
 
55
57
  declare const IGNORED_ATTRIBUTES: string[];
56
58
  declare const IGNORED_ATTRIBUTES_STARTS_WITH: string[];
57
59
 
60
+ type DependentForbiddenElement = {
61
+ code: SchemaCode;
62
+ dependentElements: SchemaCode[];
63
+ };
64
+ declare const DEPENDENT_FORBIDDEN_ELEMENTS: DependentForbiddenElement[];
65
+
58
66
  declare const ALIGNMENT_ATTRIBUTE = "data-alignment";
59
67
  declare const LEFT_INDENT_ATTRIBUTE = "data-left-indent";
60
68
  declare const RIGHT_INDENT_ATTRIBUTE = "data-right-indent";
@@ -105,6 +113,6 @@ type ValidationError = {
105
113
  * * ValidateConsecutiveFootnotes and ValidateConsecutiveFootnoteCues are default validations.
106
114
  * @returns The validation errors.
107
115
  */
108
- declare const validateXml: (xml: Element, elementsToLink: readonly string[], linkElements: readonly string[], schemasToValidateAgainst: SchemaCode[]) => ValidationError[];
116
+ declare const validateXml: (xml: Element, elementsToLink: string[], linkElements: string[], schemasToValidateAgainst: SchemaCode[]) => ValidationError[];
109
117
 
110
- export { ALIGNMENT_ATTRIBUTE, ATOM_SCHEMAS, type AllowedChildren, type AtomSchemaCode, type AttributeDefinition, ELEMENT_SCHEMAS, ELE_ID_ATTRIBUTE, ElementLabel, type ElementSchemaCode, FIGURE_PLACEMENT_ATTRIBUTE, type FreeAttribute, ID_ATTRS, IGNORED_ATTRIBUTES, IGNORED_ATTRIBUTES_STARTS_WITH, IGNORED_ELEMENT_SELECTORS, IGNORED_VALIDATION_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, RIGHT_INDENT_ATTRIBUTE, type RestrictedAttribute, SCHEMAS, SPACE_ABOVE_ATTRIBUTE, SPACE_BELOW_ATTRIBUTE, SPECIAL_INDENT_ATTRIBUTE, STORE_ATTRIBUTE, STYLE_NAME_ATTRIBUTE, type Schema, type SchemaCode, type SchemaKind, TRACK_CHANGES_ATTRS, TRACK_CHANGES_ATTR_DATA_DATE, TRACK_CHANGES_ATTR_DATA_TIME, TRACK_CHANGES_ATTR_DATA_USERID, TRACK_CHANGES_ATTR_DATA_USERNAME, type ValidationError, type ValidationErrorKind, type ValidationErrorLevel, createElement, findAttributeDefinitionByName, findSchemaByCode, findSchemaByElement, findSchemaByTagName, getAttributeName, isAttributeRestricted, isIgnoredAttribute, isIgnoredElement, isIgnoredValidation, validateXml };
118
+ export { ALIGNMENT_ATTRIBUTE, ATOM_SCHEMAS, type AllowedChildren, type AtomSchemaCode, type AttributeDefinition, 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, IGNORED_VALIDATION_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, RIGHT_INDENT_ATTRIBUTE, type RestrictedAttribute, SCHEMAS, SPACE_ABOVE_ATTRIBUTE, SPACE_BELOW_ATTRIBUTE, SPECIAL_INDENT_ATTRIBUTE, STORE_ATTRIBUTE, STYLE_NAME_ATTRIBUTE, type Schema, type SchemaCode, type SchemaKind, TRACK_CHANGES_ATTRS, TRACK_CHANGES_ATTR_DATA_DATE, TRACK_CHANGES_ATTR_DATA_TIME, TRACK_CHANGES_ATTR_DATA_USERID, TRACK_CHANGES_ATTR_DATA_USERNAME, type ValidationError, type ValidationErrorKind, type ValidationErrorLevel, createElement, findAttributeDefinitionByName, findSchemaByCode, findSchemaByElement, getAttributeName, isAttributeRestricted, isIgnoredAttribute, isIgnoredElement, isIgnoredValidation, isTrackChangesSchema, validateXml };
package/dist/index.js CHANGED
@@ -22,6 +22,7 @@ var index_exports = {};
22
22
  __export(index_exports, {
23
23
  ALIGNMENT_ATTRIBUTE: () => ALIGNMENT_ATTRIBUTE,
24
24
  ATOM_SCHEMAS: () => ATOM_SCHEMAS,
25
+ DEPENDENT_FORBIDDEN_ELEMENTS: () => DEPENDENT_FORBIDDEN_ELEMENTS,
25
26
  ELEMENT_SCHEMAS: () => ELEMENT_SCHEMAS,
26
27
  ELE_ID_ATTRIBUTE: () => ELE_ID_ATTRIBUTE,
27
28
  ElementLabel: () => ElementLabel,
@@ -55,35 +56,19 @@ __export(index_exports, {
55
56
  findAttributeDefinitionByName: () => findAttributeDefinitionByName,
56
57
  findSchemaByCode: () => findSchemaByCode,
57
58
  findSchemaByElement: () => findSchemaByElement,
58
- findSchemaByTagName: () => findSchemaByTagName,
59
59
  getAttributeName: () => getAttributeName,
60
60
  isAttributeRestricted: () => isAttributeRestricted,
61
61
  isIgnoredAttribute: () => isIgnoredAttribute,
62
62
  isIgnoredElement: () => isIgnoredElement,
63
63
  isIgnoredValidation: () => isIgnoredValidation,
64
+ isTrackChangesSchema: () => isTrackChangesSchema,
64
65
  validateXml: () => validateXml
65
66
  });
66
67
  module.exports = __toCommonJS(index_exports);
67
68
 
68
69
  // src/schema/utils/createSchema.ts
69
- var createSchema = ({
70
- code,
71
- kind,
72
- tag,
73
- requiredAttributes,
74
- optionalAttributes,
75
- defaultChildCode,
76
- allowedChildrenCodes
77
- }) => {
78
- return {
79
- code,
80
- kind,
81
- tag,
82
- requiredAttributes,
83
- optionalAttributes,
84
- defaultChildCode,
85
- allowedChildrenCodes: new Set(allowedChildrenCodes)
86
- };
70
+ var createSchema = (params) => {
71
+ return params;
87
72
  };
88
73
 
89
74
  // src/schema/shared/globalAttributes.ts
@@ -184,6 +169,7 @@ var COMMENT_QUERY_SECTION_SCHEMA = createSchema({
184
169
  kind: "atom",
185
170
  tag: SPAN_TAG,
186
171
  code: "commentQuerySection",
172
+ matchesElement: (element) => element.classList.contains("cmtQurySection"),
187
173
  requiredAttributes: [...ID_ATTRS, "id"],
188
174
  optionalAttributes: [...TRACK_CHANGES_ATTRS, "title"],
189
175
  allowedChildrenCodes: [...ATOM_CODES_ALLOWED_CHILDREN]
@@ -203,6 +189,7 @@ var DELETION_SCHEMA = createSchema({
203
189
  code: "deletion",
204
190
  kind: "atom",
205
191
  tag: SPAN_TAG,
192
+ matchesElement: (element) => element.classList.contains("del"),
206
193
  requiredAttributes: [...TRACK_CHANGES_ATTRS, ...ID_ATTRS],
207
194
  optionalAttributes: [],
208
195
  allowedChildrenCodes: [
@@ -253,6 +240,7 @@ var INSERTION_SCHEMA = createSchema({
253
240
  code: "insertion",
254
241
  kind: "atom",
255
242
  tag: SPAN_TAG,
243
+ matchesElement: (element) => element.classList.contains("ins"),
256
244
  requiredAttributes: [...TRACK_CHANGES_ATTRS, ...ID_ATTRS],
257
245
  optionalAttributes: [],
258
246
  allowedChildrenCodes: [
@@ -1974,15 +1962,21 @@ var EPIGRAPH_SCHEMA = createSchema({
1974
1962
  });
1975
1963
 
1976
1964
  // src/schema/elementSchemas/elements/footnoteSection.ts
1965
+ var FOOTNOTE_SECTION_ROLES = ["ch", "bk"];
1977
1966
  var FOOTNOTE_SECTION_SCHEMA = createSchema({
1978
1967
  code: "footnoteSection",
1979
1968
  kind: "element",
1980
1969
  tag: SECTION_TAG,
1970
+ matchesElement: (element) => {
1971
+ const role = element.getAttribute("role");
1972
+ if (role && FOOTNOTE_SECTION_ROLES.includes(role)) return true;
1973
+ return element.querySelector(FOOTNOTE_TAG) !== null;
1974
+ },
1981
1975
  requiredAttributes: [
1982
1976
  ...ID_ATTRS,
1983
1977
  {
1984
1978
  name: "role",
1985
- values: ["ch", "bk"],
1979
+ values: FOOTNOTE_SECTION_ROLES,
1986
1980
  defaultValue: "ch"
1987
1981
  }
1988
1982
  ],
@@ -1995,16 +1989,22 @@ var FOOTNOTE_SECTION_SCHEMA = createSchema({
1995
1989
  });
1996
1990
 
1997
1991
  // src/schema/elementSchemas/elements/heading.ts
1992
+ var HEADING_ROLES = ["H1", "H2", "H3", "H4", "H5", "H6", "vignette_group"];
1998
1993
  var HEADING_SCHEMA = createSchema({
1999
1994
  code: "heading",
2000
1995
  kind: "element",
2001
1996
  tag: SECTION_TAG,
1997
+ matchesElement: (element) => {
1998
+ const role = element.getAttribute("role");
1999
+ if (!role) return false;
2000
+ return HEADING_ROLES.includes(role);
2001
+ },
2002
2002
  requiredAttributes: [
2003
2003
  ...ID_ATTRS,
2004
2004
  "xmlid",
2005
2005
  {
2006
2006
  name: "role",
2007
- values: ["H1", "H2", "H3", "H4", "H5", "H6", "vignette_group"],
2007
+ values: HEADING_ROLES,
2008
2008
  defaultValue: "H1"
2009
2009
  }
2010
2010
  ],
@@ -2071,17 +2071,7 @@ var MATHPHRASE_SCHEMA = createSchema({
2071
2071
  tag: MATHPHRASE_TAG,
2072
2072
  requiredAttributes: [ELE_ID_ATTRIBUTE],
2073
2073
  optionalAttributes: [],
2074
- allowedChildrenCodes: [
2075
- { code: MATH_SCHEMA.code, required: true },
2076
- {
2077
- code: INSERTION_SCHEMA.code,
2078
- required: false
2079
- },
2080
- {
2081
- code: DELETION_SCHEMA.code,
2082
- required: false
2083
- }
2084
- ]
2074
+ allowedChildrenCodes: [{ code: MATH_SCHEMA.code, required: true }]
2085
2075
  });
2086
2076
 
2087
2077
  // src/schema/elementSchemas/elements/reference/bibliodiv.ts
@@ -2339,7 +2329,78 @@ var SCHEMAS = {
2339
2329
  ...INLINE_ELEMENT_SCHEMAS
2340
2330
  };
2341
2331
 
2342
- // src/schema/schema.types.ts
2332
+ // src/schema/shared/ignoredElements.ts
2333
+ var IGNORED_ELEMENT_SELECTORS = [
2334
+ "span.rhlabel",
2335
+ "span.printlabel",
2336
+ "span.refIcon",
2337
+ "span.missLink",
2338
+ "span.ref-drag"
2339
+ ];
2340
+ var IGNORED_VALIDATION_ELEMENT_SELECTORS = [
2341
+ "referenceBibliomixed"
2342
+ ];
2343
+
2344
+ // src/schema/shared/ignoredAttributes.ts
2345
+ var IGNORED_ATTRIBUTES = [
2346
+ // Styling & layout
2347
+ "class",
2348
+ "style",
2349
+ // Editor state
2350
+ "contenteditable",
2351
+ "draggable",
2352
+ "data-prevpos",
2353
+ "tabindex",
2354
+ // Browser behaviour
2355
+ "spellcheck",
2356
+ "translate",
2357
+ "lang",
2358
+ "dir",
2359
+ "hidden",
2360
+ "data-gramm"
2361
+ ];
2362
+ var IGNORED_ATTRIBUTES_STARTS_WITH = ["er-", "er_", "drop", "drag"];
2363
+
2364
+ // src/schema/utils/utils.ts
2365
+ var isTrackChangesSchema = (schema) => schema.code === "insertion" || schema.code === "deletion";
2366
+ var isIgnoredAttribute = (attribute) => IGNORED_ATTRIBUTES.includes(attribute) || IGNORED_ATTRIBUTES_STARTS_WITH.some(
2367
+ (startsWith) => attribute.startsWith(startsWith)
2368
+ );
2369
+ var isIgnoredElement = (element) => IGNORED_ELEMENT_SELECTORS.some((selector) => element.matches(selector));
2370
+ var isIgnoredValidation = (code) => IGNORED_VALIDATION_ELEMENT_SELECTORS.includes(code);
2371
+ var findSchemaByCode = (code) => SCHEMAS[code];
2372
+ var findSchemaByElement = (element) => {
2373
+ const schema = Object.values(SCHEMAS).find(
2374
+ (schema2) => schema2.tag == element.tagName && (!schema2.matchesElement || schema2.matchesElement(element))
2375
+ );
2376
+ return schema;
2377
+ };
2378
+ var isAttributeRestricted = (attr) => typeof attr !== "string";
2379
+ var getAttributeName = (attribute) => typeof attribute === "string" ? attribute.toLowerCase() : attribute.name.toLowerCase();
2380
+ var findAttributeDefinitionByName = (definitions, name) => {
2381
+ for (const def of definitions) {
2382
+ if (getAttributeName(def) === name.toLowerCase()) {
2383
+ return def;
2384
+ }
2385
+ }
2386
+ return void 0;
2387
+ };
2388
+
2389
+ // src/schema/utils/createElement.ts
2390
+ var createElement = ({
2391
+ schemaCode,
2392
+ attributes
2393
+ }) => {
2394
+ const schema = findSchemaByCode(schemaCode);
2395
+ const element = document.createElement(schema.tag.toLowerCase());
2396
+ for (const [name, value] of Object.entries(attributes)) {
2397
+ if (!value) continue;
2398
+ element.setAttribute(name, String(value));
2399
+ }
2400
+ return element;
2401
+ };
2402
+
2403
+ // src/schema/shared/labelElements.ts
2343
2404
  var ElementLabel = {
2344
2405
  referenceBibliomixed: "Reference Bibliomixed",
2345
2406
  referenceBibliography: "Reference Bibliography",
@@ -2464,111 +2525,13 @@ var ElementLabel = {
2464
2525
  phrase: "Phrase"
2465
2526
  };
2466
2527
 
2467
- // src/schema/shared/ignoredElements.ts
2468
- var IGNORED_ELEMENT_SELECTORS = [
2469
- "span.rhlabel",
2470
- "span.printlabel",
2471
- "span.refIcon",
2472
- "span.missLink",
2473
- "span.ref-drag"
2474
- ];
2475
- var IGNORED_VALIDATION_ELEMENT_SELECTORS = [
2476
- "referenceBibliomixed"
2477
- ];
2478
-
2479
- // src/schema/shared/ignoredAttributes.ts
2480
- var IGNORED_ATTRIBUTES = [
2481
- // Styling & layout
2482
- "class",
2483
- "style",
2484
- // Editor state
2485
- "contenteditable",
2486
- "draggable",
2487
- "data-prevpos",
2488
- "tabindex",
2489
- // Browser behaviour
2490
- "spellcheck",
2491
- "translate",
2492
- "lang",
2493
- "dir",
2494
- "hidden",
2495
- "data-gramm"
2496
- ];
2497
- var IGNORED_ATTRIBUTES_STARTS_WITH = ["er-", "er_", "drop", "drag"];
2498
-
2499
- // src/schema/utils/utils.ts
2500
- var isIgnoredAttribute = (attribute) => IGNORED_ATTRIBUTES.includes(attribute) || IGNORED_ATTRIBUTES_STARTS_WITH.some(
2501
- (startsWith) => attribute.startsWith(startsWith)
2502
- );
2503
- var isIgnoredElement = (element) => IGNORED_ELEMENT_SELECTORS.some((selector) => element.matches(selector));
2504
- var isIgnoredValidation = (code) => IGNORED_VALIDATION_ELEMENT_SELECTORS.includes(code);
2505
- var findSchemaByCode = (code) => SCHEMAS[code];
2506
- var findSchemaByTagName = (tagName) => Object.values(SCHEMAS).filter(
2507
- (schema) => schema.tag === tagName.toUpperCase()
2508
- );
2509
- var findSchemaByElement = (element) => {
2510
- const tagName = element.tagName.toUpperCase();
2511
- const isInsertion = element.classList.contains("ins");
2512
- const isDeletion = element.classList.contains("del");
2513
- const isQueryComment = element.classList.contains("cmtQurySection");
2514
- const role = element.getAttribute("role");
2515
- if (tagName === SPAN_TAG) {
2516
- if (isQueryComment) {
2517
- return findSchemaByCode("commentQuerySection");
2518
- }
2519
- if (isInsertion) {
2520
- return findSchemaByCode("insertion");
2521
- }
2522
- if (isDeletion) {
2523
- return findSchemaByCode("deletion");
2524
- }
2525
- }
2526
- if (tagName === SECTION_TAG) {
2527
- if (role) {
2528
- if (role.startsWith("H")) {
2529
- return findSchemaByCode("heading");
2530
- }
2531
- if (role === "ch" || role === "bk") {
2532
- return findSchemaByCode("footnoteSection");
2533
- }
2534
- }
2535
- const isFootnoteSectionWithoutRole = element.querySelector(FOOTNOTE_TAG);
2536
- if (isFootnoteSectionWithoutRole) {
2537
- return findSchemaByCode("footnoteSection");
2538
- }
2539
- }
2540
- const candidates = findSchemaByTagName(tagName);
2541
- return candidates[0];
2542
- };
2543
- var isAttributeRestricted = (attr) => typeof attr !== "string";
2544
- var getAttributeName = (attribute) => typeof attribute === "string" ? attribute.toLowerCase() : attribute.name.toLowerCase();
2545
- var findAttributeDefinitionByName = (definitions, name) => {
2546
- for (const def of definitions) {
2547
- if (getAttributeName(def) === name.toLowerCase()) {
2548
- return def;
2549
- }
2550
- }
2551
- return void 0;
2552
- };
2553
-
2554
- // src/schema/utils/createElement.ts
2555
- var createElement = ({
2556
- schemaCode,
2557
- attributes
2558
- }) => {
2559
- const schema = findSchemaByCode(schemaCode);
2560
- if (!schema) {
2561
- throw new Error(`Schema not found for code: ${schemaCode}`);
2562
- }
2563
- const element = document.createElement(schema.tag.toLowerCase());
2564
- for (const [name, value] of Object.entries(attributes)) {
2565
- if (value === void 0) {
2566
- continue;
2567
- }
2568
- element.setAttribute(name, String(value));
2528
+ // src/schema/shared/dependentsForbiddenElements.ts
2529
+ var DEPENDENT_FORBIDDEN_ELEMENTS = [
2530
+ {
2531
+ code: "commentQuerySection",
2532
+ dependentElements: ["query", "comment", "reply", "content"]
2569
2533
  }
2570
- return element;
2571
- };
2534
+ ];
2572
2535
 
2573
2536
  // src/validation/utils/generateValidationIssueId.ts
2574
2537
  var generateValidationIssueId = () => {
@@ -2791,12 +2754,47 @@ var validateElementAgainstSchema = (element, schema) => {
2791
2754
  errors.push(...validateChildren(element, schema));
2792
2755
  return errors;
2793
2756
  };
2794
- var validateChildren = (element, schema) => {
2757
+ var validateRequiredChildren = (element, schema, childElements) => {
2795
2758
  const errors = [];
2759
+ for (const allowedChild of schema.allowedChildrenCodes) {
2760
+ if (!allowedChild.required) {
2761
+ continue;
2762
+ }
2763
+ const hasChild = childElements.some((child) => {
2764
+ const childSchema = findSchemaByElement(child);
2765
+ return childSchema?.code === allowedChild.code;
2766
+ });
2767
+ if (!hasChild) {
2768
+ const requiredChildrenTag = findSchemaByCode(allowedChild.code).tag;
2769
+ addValidationIssue(errors, {
2770
+ store: getStore(element),
2771
+ title: `Missing required child element ${requiredChildrenTag}`,
2772
+ level: "warning",
2773
+ kind: "missing",
2774
+ description: `The child element ${requiredChildrenTag} is required but not present.`
2775
+ });
2776
+ }
2777
+ }
2778
+ return errors;
2779
+ };
2780
+ var getEffectiveChildElements = (element) => {
2781
+ const effectiveChildren = [];
2796
2782
  const childElements = [...element.children].filter(
2797
2783
  (child) => !isIgnoredElement(child)
2798
2784
  );
2799
- const allowedChildren = [...schema.allowedChildrenCodes];
2785
+ for (const child of childElements) {
2786
+ const childSchema = findSchemaByElement(child);
2787
+ if (childSchema && isTrackChangesSchema(childSchema)) {
2788
+ effectiveChildren.push(...getEffectiveChildElements(child));
2789
+ continue;
2790
+ }
2791
+ effectiveChildren.push(child);
2792
+ }
2793
+ return effectiveChildren;
2794
+ };
2795
+ var validateChildren = (element, schema) => {
2796
+ const errors = [];
2797
+ const childElements = getEffectiveChildElements(element);
2800
2798
  if (schema.kind === "atom") {
2801
2799
  return errors;
2802
2800
  }
@@ -2812,7 +2810,7 @@ var validateChildren = (element, schema) => {
2812
2810
  });
2813
2811
  continue;
2814
2812
  }
2815
- const allowedChild = allowedChildren.find(
2813
+ const allowedChild = schema.allowedChildrenCodes.find(
2816
2814
  (candidate) => candidate.code === childSchema.code
2817
2815
  );
2818
2816
  if (!allowedChild) {
@@ -2827,34 +2825,7 @@ var validateChildren = (element, schema) => {
2827
2825
  }
2828
2826
  errors.push(...validateElementAgainstSchema(child, childSchema));
2829
2827
  }
2830
- for (const allowedChild of allowedChildren) {
2831
- if (!allowedChild.required) {
2832
- continue;
2833
- }
2834
- const hasChild = childElements.some((child) => {
2835
- const childSchema = findSchemaByElement(child);
2836
- return childSchema?.code === allowedChild.code;
2837
- });
2838
- if (!hasChild) {
2839
- const requiredChildLabel = ElementLabel[allowedChild.code];
2840
- addValidationIssue(errors, {
2841
- store: getStore(element),
2842
- title: `Missing required child element ${requiredChildLabel}`,
2843
- level: "warning",
2844
- kind: "missing",
2845
- description: `The child element ${requiredChildLabel} is required but not present.`
2846
- });
2847
- }
2848
- }
2849
- if (schema.allowedChildrenCodes.size === 0 && childElements.length > 0) {
2850
- addValidationIssue(errors, {
2851
- store: getStore(element),
2852
- title: "Atom element cannot contain child elements",
2853
- level: "warning",
2854
- kind: "forbidden",
2855
- description: `The element is an atom element and cannot contain child elements.`
2856
- });
2857
- }
2828
+ errors.push(...validateRequiredChildren(element, schema, childElements));
2858
2829
  return errors;
2859
2830
  };
2860
2831
 
@@ -2907,9 +2878,9 @@ var validateElement = (element, root, elementsToLink, linkElements, schemasToVal
2907
2878
  if (schemasToValidateAgainst.includes(schema.code)) {
2908
2879
  errors.push(...validateElementAgainstSchema(element, schema));
2909
2880
  }
2910
- const role = element.getAttribute("role") || "";
2881
+ const role = element.getAttribute("role");
2911
2882
  const linkableRoles = getLinkableRoles();
2912
- if (schema.tag === LINK_SCHEMA.tag && linkableRoles.includes(role) || schema.tag === FOOTNOTE_LINK_SCHEMA.tag) {
2883
+ if (schema.tag === LINK_SCHEMA.tag && role && linkableRoles.includes(role) || schema.tag === FOOTNOTE_LINK_SCHEMA.tag) {
2913
2884
  errors.push(...validateLinkend(element, elementsToLink));
2914
2885
  }
2915
2886
  if (schema.tag === BIBLIOMIXED_SCHEMA.tag) {
@@ -2956,6 +2927,7 @@ var validateXml = (xml, elementsToLink, linkElements, schemasToValidateAgainst)
2956
2927
  0 && (module.exports = {
2957
2928
  ALIGNMENT_ATTRIBUTE,
2958
2929
  ATOM_SCHEMAS,
2930
+ DEPENDENT_FORBIDDEN_ELEMENTS,
2959
2931
  ELEMENT_SCHEMAS,
2960
2932
  ELE_ID_ATTRIBUTE,
2961
2933
  ElementLabel,
@@ -2989,11 +2961,11 @@ var validateXml = (xml, elementsToLink, linkElements, schemasToValidateAgainst)
2989
2961
  findAttributeDefinitionByName,
2990
2962
  findSchemaByCode,
2991
2963
  findSchemaByElement,
2992
- findSchemaByTagName,
2993
2964
  getAttributeName,
2994
2965
  isAttributeRestricted,
2995
2966
  isIgnoredAttribute,
2996
2967
  isIgnoredElement,
2997
2968
  isIgnoredValidation,
2969
+ isTrackChangesSchema,
2998
2970
  validateXml
2999
2971
  });
package/dist/index.mjs CHANGED
@@ -1,22 +1,6 @@
1
1
  // src/schema/utils/createSchema.ts
2
- var createSchema = ({
3
- code,
4
- kind,
5
- tag,
6
- requiredAttributes,
7
- optionalAttributes,
8
- defaultChildCode,
9
- allowedChildrenCodes
10
- }) => {
11
- return {
12
- code,
13
- kind,
14
- tag,
15
- requiredAttributes,
16
- optionalAttributes,
17
- defaultChildCode,
18
- allowedChildrenCodes: new Set(allowedChildrenCodes)
19
- };
2
+ var createSchema = (params) => {
3
+ return params;
20
4
  };
21
5
 
22
6
  // src/schema/shared/globalAttributes.ts
@@ -117,6 +101,7 @@ var COMMENT_QUERY_SECTION_SCHEMA = createSchema({
117
101
  kind: "atom",
118
102
  tag: SPAN_TAG,
119
103
  code: "commentQuerySection",
104
+ matchesElement: (element) => element.classList.contains("cmtQurySection"),
120
105
  requiredAttributes: [...ID_ATTRS, "id"],
121
106
  optionalAttributes: [...TRACK_CHANGES_ATTRS, "title"],
122
107
  allowedChildrenCodes: [...ATOM_CODES_ALLOWED_CHILDREN]
@@ -136,6 +121,7 @@ var DELETION_SCHEMA = createSchema({
136
121
  code: "deletion",
137
122
  kind: "atom",
138
123
  tag: SPAN_TAG,
124
+ matchesElement: (element) => element.classList.contains("del"),
139
125
  requiredAttributes: [...TRACK_CHANGES_ATTRS, ...ID_ATTRS],
140
126
  optionalAttributes: [],
141
127
  allowedChildrenCodes: [
@@ -186,6 +172,7 @@ var INSERTION_SCHEMA = createSchema({
186
172
  code: "insertion",
187
173
  kind: "atom",
188
174
  tag: SPAN_TAG,
175
+ matchesElement: (element) => element.classList.contains("ins"),
189
176
  requiredAttributes: [...TRACK_CHANGES_ATTRS, ...ID_ATTRS],
190
177
  optionalAttributes: [],
191
178
  allowedChildrenCodes: [
@@ -1907,15 +1894,21 @@ var EPIGRAPH_SCHEMA = createSchema({
1907
1894
  });
1908
1895
 
1909
1896
  // src/schema/elementSchemas/elements/footnoteSection.ts
1897
+ var FOOTNOTE_SECTION_ROLES = ["ch", "bk"];
1910
1898
  var FOOTNOTE_SECTION_SCHEMA = createSchema({
1911
1899
  code: "footnoteSection",
1912
1900
  kind: "element",
1913
1901
  tag: SECTION_TAG,
1902
+ matchesElement: (element) => {
1903
+ const role = element.getAttribute("role");
1904
+ if (role && FOOTNOTE_SECTION_ROLES.includes(role)) return true;
1905
+ return element.querySelector(FOOTNOTE_TAG) !== null;
1906
+ },
1914
1907
  requiredAttributes: [
1915
1908
  ...ID_ATTRS,
1916
1909
  {
1917
1910
  name: "role",
1918
- values: ["ch", "bk"],
1911
+ values: FOOTNOTE_SECTION_ROLES,
1919
1912
  defaultValue: "ch"
1920
1913
  }
1921
1914
  ],
@@ -1928,16 +1921,22 @@ var FOOTNOTE_SECTION_SCHEMA = createSchema({
1928
1921
  });
1929
1922
 
1930
1923
  // src/schema/elementSchemas/elements/heading.ts
1924
+ var HEADING_ROLES = ["H1", "H2", "H3", "H4", "H5", "H6", "vignette_group"];
1931
1925
  var HEADING_SCHEMA = createSchema({
1932
1926
  code: "heading",
1933
1927
  kind: "element",
1934
1928
  tag: SECTION_TAG,
1929
+ matchesElement: (element) => {
1930
+ const role = element.getAttribute("role");
1931
+ if (!role) return false;
1932
+ return HEADING_ROLES.includes(role);
1933
+ },
1935
1934
  requiredAttributes: [
1936
1935
  ...ID_ATTRS,
1937
1936
  "xmlid",
1938
1937
  {
1939
1938
  name: "role",
1940
- values: ["H1", "H2", "H3", "H4", "H5", "H6", "vignette_group"],
1939
+ values: HEADING_ROLES,
1941
1940
  defaultValue: "H1"
1942
1941
  }
1943
1942
  ],
@@ -2004,17 +2003,7 @@ var MATHPHRASE_SCHEMA = createSchema({
2004
2003
  tag: MATHPHRASE_TAG,
2005
2004
  requiredAttributes: [ELE_ID_ATTRIBUTE],
2006
2005
  optionalAttributes: [],
2007
- allowedChildrenCodes: [
2008
- { code: MATH_SCHEMA.code, required: true },
2009
- {
2010
- code: INSERTION_SCHEMA.code,
2011
- required: false
2012
- },
2013
- {
2014
- code: DELETION_SCHEMA.code,
2015
- required: false
2016
- }
2017
- ]
2006
+ allowedChildrenCodes: [{ code: MATH_SCHEMA.code, required: true }]
2018
2007
  });
2019
2008
 
2020
2009
  // src/schema/elementSchemas/elements/reference/bibliodiv.ts
@@ -2272,7 +2261,78 @@ var SCHEMAS = {
2272
2261
  ...INLINE_ELEMENT_SCHEMAS
2273
2262
  };
2274
2263
 
2275
- // src/schema/schema.types.ts
2264
+ // src/schema/shared/ignoredElements.ts
2265
+ var IGNORED_ELEMENT_SELECTORS = [
2266
+ "span.rhlabel",
2267
+ "span.printlabel",
2268
+ "span.refIcon",
2269
+ "span.missLink",
2270
+ "span.ref-drag"
2271
+ ];
2272
+ var IGNORED_VALIDATION_ELEMENT_SELECTORS = [
2273
+ "referenceBibliomixed"
2274
+ ];
2275
+
2276
+ // src/schema/shared/ignoredAttributes.ts
2277
+ var IGNORED_ATTRIBUTES = [
2278
+ // Styling & layout
2279
+ "class",
2280
+ "style",
2281
+ // Editor state
2282
+ "contenteditable",
2283
+ "draggable",
2284
+ "data-prevpos",
2285
+ "tabindex",
2286
+ // Browser behaviour
2287
+ "spellcheck",
2288
+ "translate",
2289
+ "lang",
2290
+ "dir",
2291
+ "hidden",
2292
+ "data-gramm"
2293
+ ];
2294
+ var IGNORED_ATTRIBUTES_STARTS_WITH = ["er-", "er_", "drop", "drag"];
2295
+
2296
+ // src/schema/utils/utils.ts
2297
+ var isTrackChangesSchema = (schema) => schema.code === "insertion" || schema.code === "deletion";
2298
+ var isIgnoredAttribute = (attribute) => IGNORED_ATTRIBUTES.includes(attribute) || IGNORED_ATTRIBUTES_STARTS_WITH.some(
2299
+ (startsWith) => attribute.startsWith(startsWith)
2300
+ );
2301
+ var isIgnoredElement = (element) => IGNORED_ELEMENT_SELECTORS.some((selector) => element.matches(selector));
2302
+ var isIgnoredValidation = (code) => IGNORED_VALIDATION_ELEMENT_SELECTORS.includes(code);
2303
+ var findSchemaByCode = (code) => SCHEMAS[code];
2304
+ var findSchemaByElement = (element) => {
2305
+ const schema = Object.values(SCHEMAS).find(
2306
+ (schema2) => schema2.tag == element.tagName && (!schema2.matchesElement || schema2.matchesElement(element))
2307
+ );
2308
+ return schema;
2309
+ };
2310
+ var isAttributeRestricted = (attr) => typeof attr !== "string";
2311
+ var getAttributeName = (attribute) => typeof attribute === "string" ? attribute.toLowerCase() : attribute.name.toLowerCase();
2312
+ var findAttributeDefinitionByName = (definitions, name) => {
2313
+ for (const def of definitions) {
2314
+ if (getAttributeName(def) === name.toLowerCase()) {
2315
+ return def;
2316
+ }
2317
+ }
2318
+ return void 0;
2319
+ };
2320
+
2321
+ // src/schema/utils/createElement.ts
2322
+ var createElement = ({
2323
+ schemaCode,
2324
+ attributes
2325
+ }) => {
2326
+ const schema = findSchemaByCode(schemaCode);
2327
+ const element = document.createElement(schema.tag.toLowerCase());
2328
+ for (const [name, value] of Object.entries(attributes)) {
2329
+ if (!value) continue;
2330
+ element.setAttribute(name, String(value));
2331
+ }
2332
+ return element;
2333
+ };
2334
+
2335
+ // src/schema/shared/labelElements.ts
2276
2336
  var ElementLabel = {
2277
2337
  referenceBibliomixed: "Reference Bibliomixed",
2278
2338
  referenceBibliography: "Reference Bibliography",
@@ -2397,111 +2457,13 @@ var ElementLabel = {
2397
2457
  phrase: "Phrase"
2398
2458
  };
2399
2459
 
2400
- // src/schema/shared/ignoredElements.ts
2401
- var IGNORED_ELEMENT_SELECTORS = [
2402
- "span.rhlabel",
2403
- "span.printlabel",
2404
- "span.refIcon",
2405
- "span.missLink",
2406
- "span.ref-drag"
2407
- ];
2408
- var IGNORED_VALIDATION_ELEMENT_SELECTORS = [
2409
- "referenceBibliomixed"
2410
- ];
2411
-
2412
- // src/schema/shared/ignoredAttributes.ts
2413
- var IGNORED_ATTRIBUTES = [
2414
- // Styling & layout
2415
- "class",
2416
- "style",
2417
- // Editor state
2418
- "contenteditable",
2419
- "draggable",
2420
- "data-prevpos",
2421
- "tabindex",
2422
- // Browser behaviour
2423
- "spellcheck",
2424
- "translate",
2425
- "lang",
2426
- "dir",
2427
- "hidden",
2428
- "data-gramm"
2429
- ];
2430
- var IGNORED_ATTRIBUTES_STARTS_WITH = ["er-", "er_", "drop", "drag"];
2431
-
2432
- // src/schema/utils/utils.ts
2433
- var isIgnoredAttribute = (attribute) => IGNORED_ATTRIBUTES.includes(attribute) || IGNORED_ATTRIBUTES_STARTS_WITH.some(
2434
- (startsWith) => attribute.startsWith(startsWith)
2435
- );
2436
- var isIgnoredElement = (element) => IGNORED_ELEMENT_SELECTORS.some((selector) => element.matches(selector));
2437
- var isIgnoredValidation = (code) => IGNORED_VALIDATION_ELEMENT_SELECTORS.includes(code);
2438
- var findSchemaByCode = (code) => SCHEMAS[code];
2439
- var findSchemaByTagName = (tagName) => Object.values(SCHEMAS).filter(
2440
- (schema) => schema.tag === tagName.toUpperCase()
2441
- );
2442
- var findSchemaByElement = (element) => {
2443
- const tagName = element.tagName.toUpperCase();
2444
- const isInsertion = element.classList.contains("ins");
2445
- const isDeletion = element.classList.contains("del");
2446
- const isQueryComment = element.classList.contains("cmtQurySection");
2447
- const role = element.getAttribute("role");
2448
- if (tagName === SPAN_TAG) {
2449
- if (isQueryComment) {
2450
- return findSchemaByCode("commentQuerySection");
2451
- }
2452
- if (isInsertion) {
2453
- return findSchemaByCode("insertion");
2454
- }
2455
- if (isDeletion) {
2456
- return findSchemaByCode("deletion");
2457
- }
2458
- }
2459
- if (tagName === SECTION_TAG) {
2460
- if (role) {
2461
- if (role.startsWith("H")) {
2462
- return findSchemaByCode("heading");
2463
- }
2464
- if (role === "ch" || role === "bk") {
2465
- return findSchemaByCode("footnoteSection");
2466
- }
2467
- }
2468
- const isFootnoteSectionWithoutRole = element.querySelector(FOOTNOTE_TAG);
2469
- if (isFootnoteSectionWithoutRole) {
2470
- return findSchemaByCode("footnoteSection");
2471
- }
2472
- }
2473
- const candidates = findSchemaByTagName(tagName);
2474
- return candidates[0];
2475
- };
2476
- var isAttributeRestricted = (attr) => typeof attr !== "string";
2477
- var getAttributeName = (attribute) => typeof attribute === "string" ? attribute.toLowerCase() : attribute.name.toLowerCase();
2478
- var findAttributeDefinitionByName = (definitions, name) => {
2479
- for (const def of definitions) {
2480
- if (getAttributeName(def) === name.toLowerCase()) {
2481
- return def;
2482
- }
2483
- }
2484
- return void 0;
2485
- };
2486
-
2487
- // src/schema/utils/createElement.ts
2488
- var createElement = ({
2489
- schemaCode,
2490
- attributes
2491
- }) => {
2492
- const schema = findSchemaByCode(schemaCode);
2493
- if (!schema) {
2494
- throw new Error(`Schema not found for code: ${schemaCode}`);
2495
- }
2496
- const element = document.createElement(schema.tag.toLowerCase());
2497
- for (const [name, value] of Object.entries(attributes)) {
2498
- if (value === void 0) {
2499
- continue;
2500
- }
2501
- element.setAttribute(name, String(value));
2460
+ // src/schema/shared/dependentsForbiddenElements.ts
2461
+ var DEPENDENT_FORBIDDEN_ELEMENTS = [
2462
+ {
2463
+ code: "commentQuerySection",
2464
+ dependentElements: ["query", "comment", "reply", "content"]
2502
2465
  }
2503
- return element;
2504
- };
2466
+ ];
2505
2467
 
2506
2468
  // src/validation/utils/generateValidationIssueId.ts
2507
2469
  var generateValidationIssueId = () => {
@@ -2724,12 +2686,47 @@ var validateElementAgainstSchema = (element, schema) => {
2724
2686
  errors.push(...validateChildren(element, schema));
2725
2687
  return errors;
2726
2688
  };
2727
- var validateChildren = (element, schema) => {
2689
+ var validateRequiredChildren = (element, schema, childElements) => {
2728
2690
  const errors = [];
2691
+ for (const allowedChild of schema.allowedChildrenCodes) {
2692
+ if (!allowedChild.required) {
2693
+ continue;
2694
+ }
2695
+ const hasChild = childElements.some((child) => {
2696
+ const childSchema = findSchemaByElement(child);
2697
+ return childSchema?.code === allowedChild.code;
2698
+ });
2699
+ if (!hasChild) {
2700
+ const requiredChildrenTag = findSchemaByCode(allowedChild.code).tag;
2701
+ addValidationIssue(errors, {
2702
+ store: getStore(element),
2703
+ title: `Missing required child element ${requiredChildrenTag}`,
2704
+ level: "warning",
2705
+ kind: "missing",
2706
+ description: `The child element ${requiredChildrenTag} is required but not present.`
2707
+ });
2708
+ }
2709
+ }
2710
+ return errors;
2711
+ };
2712
+ var getEffectiveChildElements = (element) => {
2713
+ const effectiveChildren = [];
2729
2714
  const childElements = [...element.children].filter(
2730
2715
  (child) => !isIgnoredElement(child)
2731
2716
  );
2732
- const allowedChildren = [...schema.allowedChildrenCodes];
2717
+ for (const child of childElements) {
2718
+ const childSchema = findSchemaByElement(child);
2719
+ if (childSchema && isTrackChangesSchema(childSchema)) {
2720
+ effectiveChildren.push(...getEffectiveChildElements(child));
2721
+ continue;
2722
+ }
2723
+ effectiveChildren.push(child);
2724
+ }
2725
+ return effectiveChildren;
2726
+ };
2727
+ var validateChildren = (element, schema) => {
2728
+ const errors = [];
2729
+ const childElements = getEffectiveChildElements(element);
2733
2730
  if (schema.kind === "atom") {
2734
2731
  return errors;
2735
2732
  }
@@ -2745,7 +2742,7 @@ var validateChildren = (element, schema) => {
2745
2742
  });
2746
2743
  continue;
2747
2744
  }
2748
- const allowedChild = allowedChildren.find(
2745
+ const allowedChild = schema.allowedChildrenCodes.find(
2749
2746
  (candidate) => candidate.code === childSchema.code
2750
2747
  );
2751
2748
  if (!allowedChild) {
@@ -2760,34 +2757,7 @@ var validateChildren = (element, schema) => {
2760
2757
  }
2761
2758
  errors.push(...validateElementAgainstSchema(child, childSchema));
2762
2759
  }
2763
- for (const allowedChild of allowedChildren) {
2764
- if (!allowedChild.required) {
2765
- continue;
2766
- }
2767
- const hasChild = childElements.some((child) => {
2768
- const childSchema = findSchemaByElement(child);
2769
- return childSchema?.code === allowedChild.code;
2770
- });
2771
- if (!hasChild) {
2772
- const requiredChildLabel = ElementLabel[allowedChild.code];
2773
- addValidationIssue(errors, {
2774
- store: getStore(element),
2775
- title: `Missing required child element ${requiredChildLabel}`,
2776
- level: "warning",
2777
- kind: "missing",
2778
- description: `The child element ${requiredChildLabel} is required but not present.`
2779
- });
2780
- }
2781
- }
2782
- if (schema.allowedChildrenCodes.size === 0 && childElements.length > 0) {
2783
- addValidationIssue(errors, {
2784
- store: getStore(element),
2785
- title: "Atom element cannot contain child elements",
2786
- level: "warning",
2787
- kind: "forbidden",
2788
- description: `The element is an atom element and cannot contain child elements.`
2789
- });
2790
- }
2760
+ errors.push(...validateRequiredChildren(element, schema, childElements));
2791
2761
  return errors;
2792
2762
  };
2793
2763
 
@@ -2840,9 +2810,9 @@ var validateElement = (element, root, elementsToLink, linkElements, schemasToVal
2840
2810
  if (schemasToValidateAgainst.includes(schema.code)) {
2841
2811
  errors.push(...validateElementAgainstSchema(element, schema));
2842
2812
  }
2843
- const role = element.getAttribute("role") || "";
2813
+ const role = element.getAttribute("role");
2844
2814
  const linkableRoles = getLinkableRoles();
2845
- if (schema.tag === LINK_SCHEMA.tag && linkableRoles.includes(role) || schema.tag === FOOTNOTE_LINK_SCHEMA.tag) {
2815
+ if (schema.tag === LINK_SCHEMA.tag && role && linkableRoles.includes(role) || schema.tag === FOOTNOTE_LINK_SCHEMA.tag) {
2846
2816
  errors.push(...validateLinkend(element, elementsToLink));
2847
2817
  }
2848
2818
  if (schema.tag === BIBLIOMIXED_SCHEMA.tag) {
@@ -2888,6 +2858,7 @@ var validateXml = (xml, elementsToLink, linkElements, schemasToValidateAgainst)
2888
2858
  export {
2889
2859
  ALIGNMENT_ATTRIBUTE,
2890
2860
  ATOM_SCHEMAS,
2861
+ DEPENDENT_FORBIDDEN_ELEMENTS,
2891
2862
  ELEMENT_SCHEMAS,
2892
2863
  ELE_ID_ATTRIBUTE,
2893
2864
  ElementLabel,
@@ -2921,11 +2892,11 @@ export {
2921
2892
  findAttributeDefinitionByName,
2922
2893
  findSchemaByCode,
2923
2894
  findSchemaByElement,
2924
- findSchemaByTagName,
2925
2895
  getAttributeName,
2926
2896
  isAttributeRestricted,
2927
2897
  isIgnoredAttribute,
2928
2898
  isIgnoredElement,
2929
2899
  isIgnoredValidation,
2900
+ isTrackChangesSchema,
2930
2901
  validateXml
2931
2902
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pro-editor-schema",
3
- "version": "0.1.200",
3
+ "version": "0.2.1",
4
4
  "description": "Pro Editor XML schemas",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -19,13 +19,18 @@
19
19
  "build": "tsup src/index.ts --format cjs,esm --dts",
20
20
  "dev": "tsup src/index.ts --format cjs,esm --dts --watch",
21
21
  "typecheck": "tsc --noEmit",
22
+ "lint": "eslint src",
22
23
  "test": "vitest run",
23
- "test:watch": "vitest"
24
+ "test:watch": "vitest",
25
+ "check": "npm ci && npm run typecheck && npm run lint && npm run test && npm run build"
24
26
  },
25
27
  "devDependencies": {
28
+ "@eslint/js": "^10.0.1",
29
+ "eslint": "^10.6.0",
26
30
  "happy-dom": "^20.0.10",
27
31
  "tsup": "^8.0.0",
28
32
  "typescript": "^5.0.0",
33
+ "typescript-eslint": "^8.62.1",
29
34
  "vitest": "^3.2.4"
30
35
  }
31
36
  }