pro-editor-schema 1.1.8 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -197,12 +197,14 @@ const errors = validateXml(rootElement, elementsToLink, linkElements, [
197
197
 
198
198
  - Consecutive footnote number checks
199
199
  - Consecutive footnote cue checks
200
+ - Possible weblink checks
200
201
 
201
202
  It also runs per-element checks for:
202
203
 
203
204
  - Link targets (`linkend`) on linkable elements
204
205
  - Uncited bibliographic references
205
206
  - Unreferenced footnotes
207
+ - Heading hierarchy
206
208
 
207
209
  Schema-based attribute and child validation only runs for codes listed in `schemasToValidateAgainst`.
208
210
 
package/dist/index.js CHANGED
@@ -2030,7 +2030,8 @@ var FOOTNOTE_SECTION_SCHEMA = createSchema({
2030
2030
  });
2031
2031
 
2032
2032
  // src/schema/elementSchemas/elements/heading.ts
2033
- var HEADING_ROLES = ["H1", "H2", "H3", "H4", "H5", "H6", "vignette_group"];
2033
+ var HEADING_LEVEL = ["H1", "H2", "H3", "H4", "H5", "H6"];
2034
+ var HEADING_ROLES = [...HEADING_LEVEL, "vignette_group"];
2034
2035
  var HEADING_SCHEMA = createSchema({
2035
2036
  code: "heading",
2036
2037
  kind: "element",
@@ -2655,23 +2656,22 @@ var retrieveTextIgnoringNodesWithSelector = (element, selector = ".del") => {
2655
2656
  return clonedElement.textContent || "";
2656
2657
  };
2657
2658
 
2659
+ // src/validation/utils/isDeletedElement.ts
2660
+ var isDeletedElement = (element) => {
2661
+ return !!element.closest(".del");
2662
+ };
2663
+
2658
2664
  // src/validation/utils/getElementsSelector.ts
2659
2665
  var getElementsSelector = ({
2660
2666
  element,
2661
2667
  selector,
2662
2668
  filterDeletedNodes
2663
2669
  }) => {
2664
- const elements = element.querySelectorAll(selector);
2670
+ const elements = Array.from(element.querySelectorAll(selector));
2665
2671
  if (filterDeletedNodes) {
2666
- return Array.from(elements).filter((element2) => {
2667
- const hasDeletedParent = element2.closest(".del") !== null;
2668
- if (hasDeletedParent) {
2669
- return false;
2670
- }
2671
- return true;
2672
- });
2672
+ return elements.filter((element2) => !isDeletedElement(element2));
2673
2673
  }
2674
- return Array.from(elements);
2674
+ return elements;
2675
2675
  };
2676
2676
 
2677
2677
  // src/validation/validateLinkend.ts
@@ -3010,7 +3010,7 @@ var validateReferenceCited = (referenceElement, linkElements) => {
3010
3010
  // src/validation/validateWeblink.ts
3011
3011
  var import_mark = __toESM(require("mark.js"));
3012
3012
  var EDGE_PUNCTUATION = `:;.,\\-\u2013\u2014\u2012_(){}[\\]!'"+=\xAB\xBB\u2039\u203A\u201C\u201D\u2018\u2019<>`;
3013
- 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}])?`;
3013
+ 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}])?)?`;
3014
3014
  var WEB_URI_IN_TEXT_REGEX = new RegExp(WEB_URI_PATTERN, "gi");
3015
3015
  var POSSIBLE_WEBLINK_EXCLUDE_SELECTORS = [
3016
3016
  "tag",
@@ -3056,6 +3056,76 @@ var validatePossibleWeblinks = (root) => {
3056
3056
  return errors;
3057
3057
  };
3058
3058
 
3059
+ // src/validation/utils/getParentElement.ts
3060
+ var getParentElement = ({
3061
+ element,
3062
+ schemaCode,
3063
+ filterDeletedNodes
3064
+ }) => {
3065
+ let current = element.parentElement;
3066
+ while (current) {
3067
+ const isValidParent = findSchemaByElement(current)?.code === schemaCode;
3068
+ const isFilterDeleted = filterDeletedNodes && isDeletedElement(current);
3069
+ if (isValidParent || isFilterDeleted) {
3070
+ return current;
3071
+ }
3072
+ current = current.parentElement;
3073
+ }
3074
+ return void 0;
3075
+ };
3076
+
3077
+ // src/validation/validateHeadingHierarchy.ts
3078
+ var getHierarchyLevel = (element) => {
3079
+ const role = element.getAttribute("role");
3080
+ if (!role) return void 0;
3081
+ const validHeadingLevel = HEADING_LEVEL.indexOf(role) + 1;
3082
+ if (!validHeadingLevel) return void 0;
3083
+ return validHeadingLevel;
3084
+ };
3085
+ var validateHeadingHierarchy = (element) => {
3086
+ const errors = [];
3087
+ if (isDeletedElement(element)) {
3088
+ return errors;
3089
+ }
3090
+ const level = getHierarchyLevel(element);
3091
+ if (level === void 0) {
3092
+ return errors;
3093
+ }
3094
+ const parentHeading = getParentElement({
3095
+ element,
3096
+ schemaCode: HEADING_SCHEMA.code,
3097
+ filterDeletedNodes: true
3098
+ });
3099
+ if (!parentHeading) {
3100
+ if (level !== 1) {
3101
+ addValidationIssue(errors, {
3102
+ store: getStore(element),
3103
+ title: "Missing heading H1",
3104
+ kind: "missing",
3105
+ description: `H${level} is not under an H1. Please check the heading hierarchy.`,
3106
+ level: "warning"
3107
+ });
3108
+ }
3109
+ return errors;
3110
+ }
3111
+ const parentLevel = getHierarchyLevel(parentHeading);
3112
+ if (parentLevel === void 0) {
3113
+ return errors;
3114
+ }
3115
+ const expectedLevel = parentLevel + 1;
3116
+ if (level === expectedLevel) {
3117
+ return errors;
3118
+ }
3119
+ addValidationIssue(errors, {
3120
+ store: getStore(element),
3121
+ title: "Invalid heading hierarchy",
3122
+ kind: "forbidden",
3123
+ description: `H${level} cannot follow H${parentLevel} directly. Please check the heading hierarchy.`,
3124
+ level: "warning"
3125
+ });
3126
+ return errors;
3127
+ };
3128
+
3059
3129
  // src/validation/validateXml.ts
3060
3130
  var validateElement = (element, root, elementsToLink, linkElements, schemasToValidateAgainst) => {
3061
3131
  if (isIgnoredElement(element)) {
@@ -3076,6 +3146,9 @@ var validateElement = (element, root, elementsToLink, linkElements, schemasToVal
3076
3146
  if (schema.tag === FOOTNOTE_SCHEMA.tag) {
3077
3147
  errors.push(...validateFootnoteCue(element, linkElements));
3078
3148
  }
3149
+ if (schema.code === HEADING_SCHEMA.code) {
3150
+ errors.push(...validateHeadingHierarchy(element));
3151
+ }
3079
3152
  if (isIgnoredValidation(schema.code)) {
3080
3153
  return errors;
3081
3154
  }
package/dist/index.mjs CHANGED
@@ -1950,7 +1950,8 @@ var FOOTNOTE_SECTION_SCHEMA = createSchema({
1950
1950
  });
1951
1951
 
1952
1952
  // src/schema/elementSchemas/elements/heading.ts
1953
- var HEADING_ROLES = ["H1", "H2", "H3", "H4", "H5", "H6", "vignette_group"];
1953
+ var HEADING_LEVEL = ["H1", "H2", "H3", "H4", "H5", "H6"];
1954
+ var HEADING_ROLES = [...HEADING_LEVEL, "vignette_group"];
1954
1955
  var HEADING_SCHEMA = createSchema({
1955
1956
  code: "heading",
1956
1957
  kind: "element",
@@ -2575,23 +2576,22 @@ var retrieveTextIgnoringNodesWithSelector = (element, selector = ".del") => {
2575
2576
  return clonedElement.textContent || "";
2576
2577
  };
2577
2578
 
2579
+ // src/validation/utils/isDeletedElement.ts
2580
+ var isDeletedElement = (element) => {
2581
+ return !!element.closest(".del");
2582
+ };
2583
+
2578
2584
  // src/validation/utils/getElementsSelector.ts
2579
2585
  var getElementsSelector = ({
2580
2586
  element,
2581
2587
  selector,
2582
2588
  filterDeletedNodes
2583
2589
  }) => {
2584
- const elements = element.querySelectorAll(selector);
2590
+ const elements = Array.from(element.querySelectorAll(selector));
2585
2591
  if (filterDeletedNodes) {
2586
- return Array.from(elements).filter((element2) => {
2587
- const hasDeletedParent = element2.closest(".del") !== null;
2588
- if (hasDeletedParent) {
2589
- return false;
2590
- }
2591
- return true;
2592
- });
2592
+ return elements.filter((element2) => !isDeletedElement(element2));
2593
2593
  }
2594
- return Array.from(elements);
2594
+ return elements;
2595
2595
  };
2596
2596
 
2597
2597
  // src/validation/validateLinkend.ts
@@ -2930,7 +2930,7 @@ var validateReferenceCited = (referenceElement, linkElements) => {
2930
2930
  // src/validation/validateWeblink.ts
2931
2931
  import Mark from "mark.js";
2932
2932
  var EDGE_PUNCTUATION = `:;.,\\-\u2013\u2014\u2012_(){}[\\]!'"+=\xAB\xBB\u2039\u203A\u201C\u201D\u2018\u2019<>`;
2933
- 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}])?`;
2933
+ 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}])?)?`;
2934
2934
  var WEB_URI_IN_TEXT_REGEX = new RegExp(WEB_URI_PATTERN, "gi");
2935
2935
  var POSSIBLE_WEBLINK_EXCLUDE_SELECTORS = [
2936
2936
  "tag",
@@ -2976,6 +2976,76 @@ var validatePossibleWeblinks = (root) => {
2976
2976
  return errors;
2977
2977
  };
2978
2978
 
2979
+ // src/validation/utils/getParentElement.ts
2980
+ var getParentElement = ({
2981
+ element,
2982
+ schemaCode,
2983
+ filterDeletedNodes
2984
+ }) => {
2985
+ let current = element.parentElement;
2986
+ while (current) {
2987
+ const isValidParent = findSchemaByElement(current)?.code === schemaCode;
2988
+ const isFilterDeleted = filterDeletedNodes && isDeletedElement(current);
2989
+ if (isValidParent || isFilterDeleted) {
2990
+ return current;
2991
+ }
2992
+ current = current.parentElement;
2993
+ }
2994
+ return void 0;
2995
+ };
2996
+
2997
+ // src/validation/validateHeadingHierarchy.ts
2998
+ var getHierarchyLevel = (element) => {
2999
+ const role = element.getAttribute("role");
3000
+ if (!role) return void 0;
3001
+ const validHeadingLevel = HEADING_LEVEL.indexOf(role) + 1;
3002
+ if (!validHeadingLevel) return void 0;
3003
+ return validHeadingLevel;
3004
+ };
3005
+ var validateHeadingHierarchy = (element) => {
3006
+ const errors = [];
3007
+ if (isDeletedElement(element)) {
3008
+ return errors;
3009
+ }
3010
+ const level = getHierarchyLevel(element);
3011
+ if (level === void 0) {
3012
+ return errors;
3013
+ }
3014
+ const parentHeading = getParentElement({
3015
+ element,
3016
+ schemaCode: HEADING_SCHEMA.code,
3017
+ filterDeletedNodes: true
3018
+ });
3019
+ if (!parentHeading) {
3020
+ if (level !== 1) {
3021
+ addValidationIssue(errors, {
3022
+ store: getStore(element),
3023
+ title: "Missing heading H1",
3024
+ kind: "missing",
3025
+ description: `H${level} is not under an H1. Please check the heading hierarchy.`,
3026
+ level: "warning"
3027
+ });
3028
+ }
3029
+ return errors;
3030
+ }
3031
+ const parentLevel = getHierarchyLevel(parentHeading);
3032
+ if (parentLevel === void 0) {
3033
+ return errors;
3034
+ }
3035
+ const expectedLevel = parentLevel + 1;
3036
+ if (level === expectedLevel) {
3037
+ return errors;
3038
+ }
3039
+ addValidationIssue(errors, {
3040
+ store: getStore(element),
3041
+ title: "Invalid heading hierarchy",
3042
+ kind: "forbidden",
3043
+ description: `H${level} cannot follow H${parentLevel} directly. Please check the heading hierarchy.`,
3044
+ level: "warning"
3045
+ });
3046
+ return errors;
3047
+ };
3048
+
2979
3049
  // src/validation/validateXml.ts
2980
3050
  var validateElement = (element, root, elementsToLink, linkElements, schemasToValidateAgainst) => {
2981
3051
  if (isIgnoredElement(element)) {
@@ -2996,6 +3066,9 @@ var validateElement = (element, root, elementsToLink, linkElements, schemasToVal
2996
3066
  if (schema.tag === FOOTNOTE_SCHEMA.tag) {
2997
3067
  errors.push(...validateFootnoteCue(element, linkElements));
2998
3068
  }
3069
+ if (schema.code === HEADING_SCHEMA.code) {
3070
+ errors.push(...validateHeadingHierarchy(element));
3071
+ }
2999
3072
  if (isIgnoredValidation(schema.code)) {
3000
3073
  return errors;
3001
3074
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pro-editor-schema",
3
- "version": "1.1.8",
3
+ "version": "1.2.0",
4
4
  "description": "Pro Editor XML schemas",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",