@stll/folio-core 0.11.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai-edits/apply.js +602 -50
- package/dist/ai-edits/block-identity.d.ts +4 -0
- package/dist/ai-edits/block-identity.js +9 -0
- package/dist/ai-edits/blockRange.d.ts +28 -1
- package/dist/ai-edits/blockRange.js +67 -1
- package/dist/ai-edits/headless.d.ts +4 -4
- package/dist/ai-edits/headless.js +9 -9
- package/dist/ai-edits/index.d.ts +2 -2
- package/dist/ai-edits/index.js +2 -2
- package/dist/ai-edits/read.d.ts +7 -7
- package/dist/ai-edits/read.js +110 -3
- package/dist/ai-edits/table-cell-mutations.d.ts +48 -0
- package/dist/ai-edits/table-cell-mutations.js +237 -0
- package/dist/ai-edits/table-mutation-plan.d.ts +38 -0
- package/dist/ai-edits/table-mutation-plan.js +90 -0
- package/dist/ai-edits/table-row-column-mutations.d.ts +100 -0
- package/dist/ai-edits/table-row-column-mutations.js +300 -0
- package/dist/ai-edits/table-targets.d.ts +32 -0
- package/dist/ai-edits/table-targets.js +70 -0
- package/dist/ai-edits/types.d.ts +34 -0
- package/dist/compat/eigenpal.js +1 -1
- package/dist/content-controls/mutateContentControls.js +1 -1
- package/dist/controller/activeEditorStory.d.ts +36 -0
- package/dist/controller/activeEditorStory.js +30 -0
- package/dist/controller/contentControlWidgetController.d.ts +55 -0
- package/dist/controller/contentControlWidgetController.js +141 -0
- package/dist/controller/fontReadiness.js +2 -0
- package/dist/controller/headerFooterEditorManager.js +1 -1
- package/dist/controller/hiddenEditorApi.js +2 -2
- package/dist/controller/hiddenEditorManager.d.ts +12 -1
- package/dist/controller/hiddenEditorManager.js +32 -11
- package/dist/controller/layoutPipeline.d.ts +2 -0
- package/dist/controller/layoutPipeline.js +16 -1
- package/dist/controller/noteEditorManager.d.ts +36 -0
- package/dist/controller/noteEditorManager.js +210 -0
- package/dist/document-operations.d.ts +35 -3
- package/dist/document-operations.js +161 -3
- package/dist/docx/blockContentParser.js +2 -2
- package/dist/docx/commentParser.js +6 -1
- package/dist/docx/conformance.js +8 -1
- package/dist/docx/documentGrid.d.ts +8 -0
- package/dist/docx/documentGrid.js +8 -0
- package/dist/docx/drawingUtils.js +1 -1
- package/dist/docx/ensureParaIds.js +1 -1
- package/dist/docx/fieldParser.js +1 -1
- package/dist/docx/notePropertiesParser.js +1 -1
- package/dist/docx/numberingParser.js +1 -1
- package/dist/docx/paragraphParser.js +63 -58
- package/dist/docx/paragraphTextBoxEnrichment.d.ts +2 -1
- package/dist/docx/paragraphTextBoxEnrichment.js +67 -13
- package/dist/docx/paragraphTraversal.js +1 -1
- package/dist/docx/parser.js +3 -3
- package/dist/docx/rezip.js +1 -1
- package/dist/docx/runParser.js +4 -2
- package/dist/docx/sdtProperties.js +1 -1
- package/dist/docx/sectionParser.js +1 -1
- package/dist/docx/selectiveSave.js +1 -1
- package/dist/docx/serializer/commentSerializer.js +1 -2
- package/dist/docx/serializer/documentSerializer.js +2 -2
- package/dist/docx/serializer/headerFooterSerializer.js +2 -2
- package/dist/docx/serializer/noteSerializer.js +2 -2
- package/dist/docx/serializer/paragraphSerializer.js +7 -1
- package/dist/docx/serializer/runSerializer.js +4 -2
- package/dist/docx/serializer/stylesSerializer.js +1 -1
- package/dist/docx/serializer/tableSerializer.d.ts +5 -4
- package/dist/docx/serializer/tableSerializer.js +17 -13
- package/dist/docx/server/applyDocxXmlPatchProposal.d.ts +87 -0
- package/dist/docx/server/applyDocxXmlPatchProposal.js +204 -0
- package/dist/docx/server/boundedArchive.d.ts +14 -3
- package/dist/docx/server/boundedArchive.js +56 -11
- package/dist/docx/server/evaluateDocxXmlPatchProposal.d.ts +85 -0
- package/dist/docx/server/evaluateDocxXmlPatchProposal.js +361 -0
- package/dist/docx/server/inspectDocxPackage.d.ts +70 -0
- package/dist/docx/server/inspectDocxPackage.js +217 -0
- package/dist/docx/server/validateDocxConformance.d.ts +41 -0
- package/dist/docx/server/validateDocxConformance.js +336 -0
- package/dist/docx/settingsParser.js +11 -1
- package/dist/docx/shapeParser.js +1 -1
- package/dist/docx/styleParser.js +4 -2
- package/dist/docx/tableParser.js +18 -7
- package/dist/docx/textBoxParser.d.ts +3 -3
- package/dist/docx/textBoxParser.js +16 -12
- package/dist/docx/xmlSafety.d.ts +5 -0
- package/dist/docx/xmlSafety.js +9 -0
- package/dist/fields/resolveFieldValues.js +1 -3
- package/dist/i18n/messages/catalogs.gen.d.ts +17 -0
- package/dist/i18n/messages/catalogs.gen.js +17 -0
- package/dist/i18n/messages/messages.gen.d.ts +1 -0
- package/dist/index.js +1 -1
- package/dist/layout-bridge/convert/footnoteLayout.d.ts +1 -0
- package/dist/layout-bridge/convert/footnoteLayout.js +47 -40
- package/dist/layout-bridge/convert/headerFooterLayout.d.ts +1 -0
- package/dist/layout-bridge/convert/headerFooterLayout.js +3 -0
- package/dist/layout-bridge/convert/templatePreviewFlow.js +3 -3
- package/dist/layout-bridge/convert/toFlowBlocks.d.ts +4 -2
- package/dist/layout-bridge/convert/toFlowBlocks.js +136 -20
- package/dist/layout-bridge/dom/noteStoryDom.d.ts +6 -0
- package/dist/layout-bridge/dom/noteStoryDom.js +17 -0
- package/dist/layout-bridge/engine/clickToPosition.js +2 -2
- package/dist/layout-engine/index.d.ts +2 -2
- package/dist/layout-engine/index.js +43 -28
- package/dist/layout-engine/justificationCompatibility.d.ts +6 -0
- package/dist/layout-engine/justificationCompatibility.js +8 -0
- package/dist/layout-engine/keep-together.js +11 -1
- package/dist/layout-engine/measure/cache.js +21 -5
- package/dist/layout-engine/measure/lineBreakProvider.js +53 -34
- package/dist/layout-engine/measure/measureBlocks.js +6 -3
- package/dist/layout-engine/measure/measureContainer.js +1 -1
- package/dist/layout-engine/measure/measureHelpers.d.ts +1 -1
- package/dist/layout-engine/measure/measureHelpers.js +3 -3
- package/dist/layout-engine/measure/measureParagraph.js +69 -25
- package/dist/layout-engine/measure/measureProvider.js +4 -3
- package/dist/layout-engine/measure/tableCellFlow.js +5 -2
- package/dist/layout-engine/measure/textBoxParagraphLayout.d.ts +9 -7
- package/dist/layout-engine/measure/textBoxParagraphLayout.js +22 -6
- package/dist/layout-engine/measure/textMeasurementPolicy.d.ts +3 -1
- package/dist/layout-engine/measure/textMeasurementPolicy.js +7 -1
- package/dist/layout-engine/paginator.js +5 -1
- package/dist/layout-engine/paragraphSpacing.d.ts +28 -4
- package/dist/layout-engine/paragraphSpacing.js +107 -13
- package/dist/layout-engine/renderedBreakReconciliation.js +1 -1
- package/dist/layout-engine/types.d.ts +26 -13
- package/dist/layout-painter/registry/modules/textBox.js +5 -1
- package/dist/layout-painter/renderPage.d.ts +5 -2
- package/dist/layout-painter/renderPage.js +56 -12
- package/dist/layout-painter/renderParagraph.js +22 -6
- package/dist/layout-painter/renderTable.d.ts +5 -1
- package/dist/layout-painter/renderTable.js +5 -2
- package/dist/layout-painter/renderTextBox.d.ts +2 -1
- package/dist/layout-painter/renderTextBox.js +25 -14
- package/dist/managers/DocumentLoaderManager.js +1 -1
- package/dist/model.js +1 -1
- package/dist/prosemirror/attrs/index.d.ts +4 -2
- package/dist/prosemirror/attrs/index.js +154 -18
- package/dist/prosemirror/commands/comments.d.ts +3 -3
- package/dist/prosemirror/commands/comments.js +248 -12
- package/dist/prosemirror/commands/contentControls.js +1 -1
- package/dist/prosemirror/commands/index.js +1 -1
- package/dist/prosemirror/commands/pageBreak.js +1 -1
- package/dist/prosemirror/commands/propertyChangeScope.d.ts +2 -2
- package/dist/prosemirror/commands/propertyChangeScope.js +3 -2
- package/dist/prosemirror/commands/tableCellMergeResolution.d.ts +9 -0
- package/dist/prosemirror/commands/tableCellMergeResolution.js +181 -0
- package/dist/prosemirror/conversion/fromProseDoc.d.ts +2 -1
- package/dist/prosemirror/conversion/fromProseDoc.js +286 -61
- package/dist/prosemirror/conversion/sdtAttrs.d.ts +9 -0
- package/dist/prosemirror/conversion/sdtAttrs.js +49 -0
- package/dist/prosemirror/conversion/toProseDoc.d.ts +2 -1
- package/dist/prosemirror/conversion/toProseDoc.js +347 -98
- package/dist/prosemirror/extensions/ExtensionManager.js +1 -1
- package/dist/prosemirror/extensions/StarterKit.js +6 -1
- package/dist/prosemirror/extensions/core/ParagraphExtension.js +4 -1
- package/dist/prosemirror/extensions/features/BaseKeymapExtension.js +1 -0
- package/dist/prosemirror/extensions/features/ListExtension.js +1 -1
- package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.d.ts +10 -1
- package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.js +93 -2
- package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.d.ts +2 -1
- package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.js +6 -2
- package/dist/prosemirror/extensions/marks/CharacterSpacingExtension.js +1 -1
- package/dist/prosemirror/extensions/marks/TrackedChangeExtensions.d.ts +9 -1
- package/dist/prosemirror/extensions/marks/TrackedChangeExtensions.js +24 -1
- package/dist/prosemirror/extensions/nodes/FieldExtension.js +1 -1
- package/dist/prosemirror/extensions/nodes/RenderedPageBreakExtension.d.ts +7 -0
- package/dist/prosemirror/extensions/nodes/RenderedPageBreakExtension.js +22 -0
- package/dist/prosemirror/extensions/nodes/TableExtension.js +9 -3
- package/dist/prosemirror/extensions/nodes/TextBoxAnchorExtension.d.ts +13 -0
- package/dist/prosemirror/extensions/nodes/TextBoxAnchorExtension.js +41 -0
- package/dist/prosemirror/extensions/nodes/TextBoxExtension.d.ts +5 -2
- package/dist/prosemirror/extensions/nodes/TextBoxExtension.js +4 -1
- package/dist/prosemirror/plugins/contentControlWidgets.d.ts +34 -8
- package/dist/prosemirror/plugins/contentControlWidgets.js +125 -91
- package/dist/prosemirror/plugins/selectionTracker.js +1 -0
- package/dist/prosemirror/plugins/suggestionMode.js +1 -1
- package/dist/prosemirror/schema/index.d.ts +2 -2
- package/dist/prosemirror/schema/marks.d.ts +5 -1
- package/dist/prosemirror/schema/nodes.d.ts +64 -7
- package/dist/prosemirror/selectionState.js +1 -0
- package/dist/prosemirror/styles/resolvedStyleAttrs.js +1 -0
- package/dist/prosemirror/tableCellMergeRevision.d.ts +9 -0
- package/dist/prosemirror/tableCellMergeRevision.js +22 -0
- package/dist/prosemirror/textBoxAnchorAttrs.d.ts +9 -0
- package/dist/prosemirror/textBoxAnchorAttrs.js +33 -0
- package/dist/prosemirror/utils/extractTrackedChanges.d.ts +1 -1
- package/dist/prosemirror/utils/extractTrackedChanges.js +62 -14
- package/dist/prosemirror/validation.js +9 -1
- package/dist/server.d.ts +6 -2
- package/dist/server.js +5 -1
- package/dist/utils/fontFamilyMerge.js +9 -11
- package/dist/utils/fontLoader.js +3 -2
- package/dist/utils/fontResolver.js +22 -4
- package/dist/utils/fontWeights.d.ts +1 -1
- package/dist/utils/fontWeights.js +1 -1
- package/dist/utils/paragraphFormattingMerge.js +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { document_d_exports } from "../../types/document.js";
|
|
2
|
+
import { DocxArchiveOptions } from "./boundedArchive.js";
|
|
3
|
+
|
|
4
|
+
//#region src/docx/server/validateDocxConformance.d.ts
|
|
5
|
+
declare const FOLIO_DOCX_CONFORMANCE_REPORT_VERSION: 1;
|
|
6
|
+
declare const FOLIO_DOCX_CONFORMANCE_PROFILE: "folio-supported-v1";
|
|
7
|
+
declare const FOLIO_DOCX_CONFORMANCE_CHECKS: readonly ["archive-safety", "required-parts", "xml-well-formedness", "package-roots", "conformance-class", "canonical-model"];
|
|
8
|
+
declare const FOLIO_DOCX_CONFORMANCE_ISSUE_CODES: readonly ["archive-load-failed", "archive-invalid-options", "archive-input-too-large", "archive-too-many-entries", "archive-entry-too-large", "archive-total-too-large", "required-part-missing", "xml-doctype-forbidden", "xml-not-well-formed", "xml-read-failed", "required-xml-unreadable", "package-root-invalid", "conformance-class-unknown", "model-invalid", "model-warning", "parser-recovery", "parser-unsupported", "parser-failed", "encrypted-container", "container-not-zip"];
|
|
9
|
+
type FolioDocxConformanceCheckId = (typeof FOLIO_DOCX_CONFORMANCE_CHECKS)[number];
|
|
10
|
+
type FolioDocxConformanceIssueCode = (typeof FOLIO_DOCX_CONFORMANCE_ISSUE_CODES)[number];
|
|
11
|
+
type FolioDocxConformanceCheckStatus = "passed" | "failed" | "indeterminate" | "not-run";
|
|
12
|
+
type FolioDocxConformanceStatus = "invalid" | "conformant" | "indeterminate";
|
|
13
|
+
type FolioDocxConformanceIssue = {
|
|
14
|
+
readonly check: FolioDocxConformanceCheckId;
|
|
15
|
+
readonly code: FolioDocxConformanceIssueCode;
|
|
16
|
+
readonly message: string;
|
|
17
|
+
readonly severity: "error" | "warning";
|
|
18
|
+
readonly part?: string;
|
|
19
|
+
readonly modelPath?: string;
|
|
20
|
+
readonly count?: number;
|
|
21
|
+
};
|
|
22
|
+
type FolioDocxConformanceCheck = {
|
|
23
|
+
readonly id: FolioDocxConformanceCheckId;
|
|
24
|
+
readonly status: FolioDocxConformanceCheckStatus;
|
|
25
|
+
};
|
|
26
|
+
type FolioDocxConformanceReport = {
|
|
27
|
+
readonly version: typeof FOLIO_DOCX_CONFORMANCE_REPORT_VERSION;
|
|
28
|
+
readonly profile: typeof FOLIO_DOCX_CONFORMANCE_PROFILE;
|
|
29
|
+
readonly status: FolioDocxConformanceStatus;
|
|
30
|
+
readonly conformanceClass: document_d_exports.DocxConformanceClass;
|
|
31
|
+
readonly checks: readonly FolioDocxConformanceCheck[];
|
|
32
|
+
readonly issues: readonly FolioDocxConformanceIssue[];
|
|
33
|
+
readonly unverifiedStandardsDimensions: readonly ["complete-schema-constraints", "markup-compatibility-processing", "consumer-specific-rendering"];
|
|
34
|
+
};
|
|
35
|
+
type ValidateDocxConformanceOptions = {
|
|
36
|
+
readonly archive?: DocxArchiveOptions;
|
|
37
|
+
};
|
|
38
|
+
/** Assess a DOCX package against Folio's named, versioned support profile. */
|
|
39
|
+
declare const validateDocxConformance: (bytes: ArrayBuffer | Uint8Array, options?: ValidateDocxConformanceOptions) => Promise<FolioDocxConformanceReport>;
|
|
40
|
+
//#endregion
|
|
41
|
+
export { FOLIO_DOCX_CONFORMANCE_CHECKS, FOLIO_DOCX_CONFORMANCE_ISSUE_CODES, FOLIO_DOCX_CONFORMANCE_PROFILE, FOLIO_DOCX_CONFORMANCE_REPORT_VERSION, FolioDocxConformanceCheck, FolioDocxConformanceCheckId, FolioDocxConformanceCheckStatus, FolioDocxConformanceIssue, FolioDocxConformanceIssueCode, FolioDocxConformanceReport, FolioDocxConformanceStatus, ValidateDocxConformanceOptions, validateDocxConformance };
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
import { findChildByLocalName, getAttributeAnyPrefix, getLocalName, getNamespacePrefix, parseXmlDocument } from "../xmlParser.js";
|
|
2
|
+
import { detectDocxConformanceClass } from "../conformance.js";
|
|
3
|
+
import { DocxModelValidationError, validateFolioDocumentModel } from "../modelValidation.js";
|
|
4
|
+
import { DOCX_CONTAINER_TYPES, detectDocxContainerType } from "../encryption/containerFormat.js";
|
|
5
|
+
import { DocxParseError, parseDocx } from "../parser.js";
|
|
6
|
+
import { DocxArchiveError, loadDocxArchive } from "./boundedArchive.js";
|
|
7
|
+
import { getDocxXmlSafetyIssue } from "../xmlSafety.js";
|
|
8
|
+
import { DOCX_CONFORMANCE_CLASSES } from "@stll/docx-core/model";
|
|
9
|
+
//#region src/docx/server/validateDocxConformance.ts
|
|
10
|
+
const FOLIO_DOCX_CONFORMANCE_REPORT_VERSION = 1;
|
|
11
|
+
const FOLIO_DOCX_CONFORMANCE_PROFILE = "folio-supported-v1";
|
|
12
|
+
const FOLIO_DOCX_CONFORMANCE_CHECKS = Object.freeze([
|
|
13
|
+
"archive-safety",
|
|
14
|
+
"required-parts",
|
|
15
|
+
"xml-well-formedness",
|
|
16
|
+
"package-roots",
|
|
17
|
+
"conformance-class",
|
|
18
|
+
"canonical-model"
|
|
19
|
+
]);
|
|
20
|
+
const FOLIO_DOCX_CONFORMANCE_ISSUE_CODES = Object.freeze([
|
|
21
|
+
"archive-load-failed",
|
|
22
|
+
"archive-invalid-options",
|
|
23
|
+
"archive-input-too-large",
|
|
24
|
+
"archive-too-many-entries",
|
|
25
|
+
"archive-entry-too-large",
|
|
26
|
+
"archive-total-too-large",
|
|
27
|
+
"required-part-missing",
|
|
28
|
+
"xml-doctype-forbidden",
|
|
29
|
+
"xml-not-well-formed",
|
|
30
|
+
"xml-read-failed",
|
|
31
|
+
"required-xml-unreadable",
|
|
32
|
+
"package-root-invalid",
|
|
33
|
+
"conformance-class-unknown",
|
|
34
|
+
"model-invalid",
|
|
35
|
+
"model-warning",
|
|
36
|
+
"parser-recovery",
|
|
37
|
+
"parser-unsupported",
|
|
38
|
+
"parser-failed",
|
|
39
|
+
"encrypted-container",
|
|
40
|
+
"container-not-zip"
|
|
41
|
+
]);
|
|
42
|
+
const REQUIRED_PARTS = Object.freeze([
|
|
43
|
+
"[Content_Types].xml",
|
|
44
|
+
"_rels/.rels",
|
|
45
|
+
"word/document.xml"
|
|
46
|
+
]);
|
|
47
|
+
const XML_PART_PATTERN = /(?:\.xml|\.rels)$/u;
|
|
48
|
+
const DOCUMENT_CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml";
|
|
49
|
+
const CONTENT_TYPES_NAMESPACE = "http://schemas.openxmlformats.org/package/2006/content-types";
|
|
50
|
+
const RELATIONSHIPS_NAMESPACE = "http://schemas.openxmlformats.org/package/2006/relationships";
|
|
51
|
+
const UNVERIFIED_STANDARDS_DIMENSIONS = Object.freeze([
|
|
52
|
+
"complete-schema-constraints",
|
|
53
|
+
"markup-compatibility-processing",
|
|
54
|
+
"consumer-specific-rendering"
|
|
55
|
+
]);
|
|
56
|
+
const createMutableReport = () => ({
|
|
57
|
+
conformanceClass: DOCX_CONFORMANCE_CLASSES.UNKNOWN,
|
|
58
|
+
checks: new Map(FOLIO_DOCX_CONFORMANCE_CHECKS.map((id) => [id, "not-run"])),
|
|
59
|
+
issues: []
|
|
60
|
+
});
|
|
61
|
+
const addIssue = (report, issue) => {
|
|
62
|
+
report.issues.push(issue);
|
|
63
|
+
};
|
|
64
|
+
const finishReport = (report) => {
|
|
65
|
+
const checks = FOLIO_DOCX_CONFORMANCE_CHECKS.map((id) => ({
|
|
66
|
+
id,
|
|
67
|
+
status: report.checks.get(id) ?? "not-run"
|
|
68
|
+
}));
|
|
69
|
+
const hasFailedCheck = checks.some(({ status }) => status === "failed");
|
|
70
|
+
const hasIndeterminateCheck = checks.some(({ status }) => status === "indeterminate");
|
|
71
|
+
let status = "conformant";
|
|
72
|
+
if (hasFailedCheck) status = "invalid";
|
|
73
|
+
else if (hasIndeterminateCheck || checks.some(({ status: checkStatus }) => checkStatus === "not-run")) status = "indeterminate";
|
|
74
|
+
return {
|
|
75
|
+
version: 1,
|
|
76
|
+
profile: FOLIO_DOCX_CONFORMANCE_PROFILE,
|
|
77
|
+
status,
|
|
78
|
+
conformanceClass: report.conformanceClass,
|
|
79
|
+
checks,
|
|
80
|
+
issues: report.issues,
|
|
81
|
+
unverifiedStandardsDimensions: UNVERIFIED_STANDARDS_DIMENSIONS
|
|
82
|
+
};
|
|
83
|
+
};
|
|
84
|
+
const validateRequiredParts = (archive, report) => {
|
|
85
|
+
const entries = new Set(archive.entries);
|
|
86
|
+
const missingParts = REQUIRED_PARTS.filter((path) => !entries.has(path));
|
|
87
|
+
if (missingParts.length === 0) {
|
|
88
|
+
report.checks.set("required-parts", "passed");
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
report.checks.set("required-parts", "failed");
|
|
92
|
+
for (const part of missingParts) addIssue(report, {
|
|
93
|
+
check: "required-parts",
|
|
94
|
+
code: "required-part-missing",
|
|
95
|
+
message: "A required package part is missing.",
|
|
96
|
+
severity: "error",
|
|
97
|
+
part
|
|
98
|
+
});
|
|
99
|
+
return false;
|
|
100
|
+
};
|
|
101
|
+
const validateXmlParts = async (archive, report) => {
|
|
102
|
+
const xmlByPath = /* @__PURE__ */ new Map();
|
|
103
|
+
let hasInvalidXml = false;
|
|
104
|
+
for (const part of archive.entries.filter((path) => XML_PART_PATTERN.test(path)).toSorted()) {
|
|
105
|
+
const xml = await archive.readEntryString(part);
|
|
106
|
+
if (xml === null) continue;
|
|
107
|
+
xmlByPath.set(part, xml);
|
|
108
|
+
const safetyIssue = getDocxXmlSafetyIssue(xml);
|
|
109
|
+
if (safetyIssue === "doctype-forbidden") {
|
|
110
|
+
hasInvalidXml = true;
|
|
111
|
+
addIssue(report, {
|
|
112
|
+
check: "xml-well-formedness",
|
|
113
|
+
code: "xml-doctype-forbidden",
|
|
114
|
+
message: "XML package parts must not declare a document type.",
|
|
115
|
+
severity: "error",
|
|
116
|
+
part
|
|
117
|
+
});
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
if (safetyIssue === "not-well-formed") {
|
|
121
|
+
hasInvalidXml = true;
|
|
122
|
+
addIssue(report, {
|
|
123
|
+
check: "xml-well-formedness",
|
|
124
|
+
code: "xml-not-well-formed",
|
|
125
|
+
message: "An XML package part is not well formed.",
|
|
126
|
+
severity: "error",
|
|
127
|
+
part
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (hasInvalidXml) {
|
|
132
|
+
report.checks.set("xml-well-formedness", "failed");
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
report.checks.set("xml-well-formedness", "passed");
|
|
136
|
+
const contentTypesXml = xmlByPath.get("[Content_Types].xml");
|
|
137
|
+
const packageRelationshipsXml = xmlByPath.get("_rels/.rels");
|
|
138
|
+
const documentXml = xmlByPath.get("word/document.xml");
|
|
139
|
+
if (contentTypesXml === void 0 || packageRelationshipsXml === void 0 || documentXml === void 0) {
|
|
140
|
+
report.checks.set("xml-well-formedness", "indeterminate");
|
|
141
|
+
addIssue(report, {
|
|
142
|
+
check: "xml-well-formedness",
|
|
143
|
+
code: "required-xml-unreadable",
|
|
144
|
+
message: "A required XML package part could not be read.",
|
|
145
|
+
severity: "error"
|
|
146
|
+
});
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
return {
|
|
150
|
+
contentTypesXml,
|
|
151
|
+
packageRelationshipsXml,
|
|
152
|
+
documentXml
|
|
153
|
+
};
|
|
154
|
+
};
|
|
155
|
+
const hasRootNamespace = ({ name, attributes, expected }) => {
|
|
156
|
+
const prefix = getNamespacePrefix(name);
|
|
157
|
+
const attribute = prefix === null ? "xmlns" : `xmlns:${prefix}`;
|
|
158
|
+
return attributes?.[attribute] === expected;
|
|
159
|
+
};
|
|
160
|
+
const hasDocumentContentType = (contentTypesXml) => {
|
|
161
|
+
const root = parseXmlDocument(contentTypesXml);
|
|
162
|
+
if (root === null || getLocalName(root.name ?? "") !== "Types" || !hasRootNamespace({
|
|
163
|
+
name: root.name ?? "",
|
|
164
|
+
attributes: root.attributes,
|
|
165
|
+
expected: CONTENT_TYPES_NAMESPACE
|
|
166
|
+
})) return false;
|
|
167
|
+
return root.elements?.some((element) => element.type === "element" && getLocalName(element.name ?? "") === "Override" && getAttributeAnyPrefix(element, "PartName") === "/word/document.xml" && getAttributeAnyPrefix(element, "ContentType") === DOCUMENT_CONTENT_TYPE) ?? false;
|
|
168
|
+
};
|
|
169
|
+
const hasMainDocumentRelationship = (packageRelationshipsXml) => {
|
|
170
|
+
const root = parseXmlDocument(packageRelationshipsXml);
|
|
171
|
+
if (root === null || getLocalName(root.name ?? "") !== "Relationships" || !hasRootNamespace({
|
|
172
|
+
name: root.name ?? "",
|
|
173
|
+
attributes: root.attributes,
|
|
174
|
+
expected: RELATIONSHIPS_NAMESPACE
|
|
175
|
+
})) return false;
|
|
176
|
+
return root.elements?.some((element) => {
|
|
177
|
+
if (element.type !== "element" || getLocalName(element.name ?? "") !== "Relationship") return false;
|
|
178
|
+
const type = getAttributeAnyPrefix(element, "Type");
|
|
179
|
+
const target = getAttributeAnyPrefix(element, "Target")?.replace(/^\.?\//u, "");
|
|
180
|
+
return type?.endsWith("/officeDocument") === true && target === "word/document.xml" && getAttributeAnyPrefix(element, "TargetMode") !== "External";
|
|
181
|
+
}) ?? false;
|
|
182
|
+
};
|
|
183
|
+
const hasDocumentRoot = (documentXml) => {
|
|
184
|
+
const root = parseXmlDocument(documentXml);
|
|
185
|
+
return root !== null && getLocalName(root.name ?? "") === "document" && findChildByLocalName(root, "body") !== null;
|
|
186
|
+
};
|
|
187
|
+
const validatePackageRoots = (xml, report) => {
|
|
188
|
+
const invalidParts = [];
|
|
189
|
+
if (!hasDocumentContentType(xml.contentTypesXml)) invalidParts.push("[Content_Types].xml");
|
|
190
|
+
if (!hasMainDocumentRelationship(xml.packageRelationshipsXml)) invalidParts.push("_rels/.rels");
|
|
191
|
+
if (!hasDocumentRoot(xml.documentXml)) invalidParts.push("word/document.xml");
|
|
192
|
+
if (invalidParts.length === 0) {
|
|
193
|
+
report.checks.set("package-roots", "passed");
|
|
194
|
+
return true;
|
|
195
|
+
}
|
|
196
|
+
report.checks.set("package-roots", "failed");
|
|
197
|
+
for (const part of invalidParts) addIssue(report, {
|
|
198
|
+
check: "package-roots",
|
|
199
|
+
code: "package-root-invalid",
|
|
200
|
+
message: "A required package part does not declare the expected root or main document binding.",
|
|
201
|
+
severity: "error",
|
|
202
|
+
part
|
|
203
|
+
});
|
|
204
|
+
return false;
|
|
205
|
+
};
|
|
206
|
+
const validateConformanceClass = (documentXml, report) => {
|
|
207
|
+
report.conformanceClass = detectDocxConformanceClass(documentXml);
|
|
208
|
+
if (report.conformanceClass !== DOCX_CONFORMANCE_CLASSES.UNKNOWN) {
|
|
209
|
+
report.checks.set("conformance-class", "passed");
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
report.checks.set("conformance-class", "indeterminate");
|
|
213
|
+
addIssue(report, {
|
|
214
|
+
check: "conformance-class",
|
|
215
|
+
code: "conformance-class-unknown",
|
|
216
|
+
message: "The main document namespace does not identify a supported conformance class.",
|
|
217
|
+
severity: "warning",
|
|
218
|
+
part: "word/document.xml"
|
|
219
|
+
});
|
|
220
|
+
};
|
|
221
|
+
const findModelValidationError = (error) => {
|
|
222
|
+
let current = error;
|
|
223
|
+
while (current instanceof Error) {
|
|
224
|
+
if (current instanceof DocxModelValidationError) return current;
|
|
225
|
+
current = "cause" in current ? current.cause : void 0;
|
|
226
|
+
}
|
|
227
|
+
return null;
|
|
228
|
+
};
|
|
229
|
+
const validateCanonicalModel = async (bytes, report) => {
|
|
230
|
+
try {
|
|
231
|
+
const document = await parseDocx(bytes, {
|
|
232
|
+
detectVariables: false,
|
|
233
|
+
preloadFonts: false
|
|
234
|
+
});
|
|
235
|
+
const validation = validateFolioDocumentModel(document);
|
|
236
|
+
const warningCount = document.warnings?.length ?? 0;
|
|
237
|
+
if (!validation.valid) report.checks.set("canonical-model", "failed");
|
|
238
|
+
else if (warningCount > 0) {
|
|
239
|
+
report.checks.set("canonical-model", "indeterminate");
|
|
240
|
+
addIssue(report, {
|
|
241
|
+
check: "canonical-model",
|
|
242
|
+
code: "parser-recovery",
|
|
243
|
+
message: "The canonical parser reported recovery or preservation warnings.",
|
|
244
|
+
severity: "warning",
|
|
245
|
+
count: warningCount
|
|
246
|
+
});
|
|
247
|
+
} else report.checks.set("canonical-model", "passed");
|
|
248
|
+
for (const issue of validation.issues) addIssue(report, {
|
|
249
|
+
check: "canonical-model",
|
|
250
|
+
code: issue.severity === "error" ? "model-invalid" : "model-warning",
|
|
251
|
+
message: issue.message,
|
|
252
|
+
severity: issue.severity,
|
|
253
|
+
modelPath: issue.path
|
|
254
|
+
});
|
|
255
|
+
} catch (error) {
|
|
256
|
+
const modelError = findModelValidationError(error);
|
|
257
|
+
if (modelError !== null) {
|
|
258
|
+
report.checks.set("canonical-model", "failed");
|
|
259
|
+
for (const issue of modelError.issues) addIssue(report, {
|
|
260
|
+
check: "canonical-model",
|
|
261
|
+
code: issue.severity === "error" ? "model-invalid" : "model-warning",
|
|
262
|
+
message: issue.message,
|
|
263
|
+
severity: issue.severity,
|
|
264
|
+
modelPath: issue.path
|
|
265
|
+
});
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
report.checks.set("canonical-model", "indeterminate");
|
|
269
|
+
addIssue(report, {
|
|
270
|
+
check: "canonical-model",
|
|
271
|
+
code: error instanceof DocxParseError ? "parser-unsupported" : "parser-failed",
|
|
272
|
+
message: "The canonical document model could not be assessed.",
|
|
273
|
+
severity: "warning"
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
};
|
|
277
|
+
/** Assess a DOCX package against Folio's named, versioned support profile. */
|
|
278
|
+
const validateDocxConformance = async (bytes, options = {}) => {
|
|
279
|
+
const report = createMutableReport();
|
|
280
|
+
const containerType = detectDocxContainerType(bytes);
|
|
281
|
+
if (containerType === DOCX_CONTAINER_TYPES.CFB) {
|
|
282
|
+
report.checks.set("archive-safety", "indeterminate");
|
|
283
|
+
addIssue(report, {
|
|
284
|
+
check: "archive-safety",
|
|
285
|
+
code: "encrypted-container",
|
|
286
|
+
message: "Encrypted containers require decryption before this profile can assess them.",
|
|
287
|
+
severity: "warning"
|
|
288
|
+
});
|
|
289
|
+
return finishReport(report);
|
|
290
|
+
}
|
|
291
|
+
if (containerType !== DOCX_CONTAINER_TYPES.ZIP) {
|
|
292
|
+
report.checks.set("archive-safety", "failed");
|
|
293
|
+
addIssue(report, {
|
|
294
|
+
check: "archive-safety",
|
|
295
|
+
code: "container-not-zip",
|
|
296
|
+
message: "The input is not a supported DOCX package container.",
|
|
297
|
+
severity: "error"
|
|
298
|
+
});
|
|
299
|
+
return finishReport(report);
|
|
300
|
+
}
|
|
301
|
+
let archive;
|
|
302
|
+
try {
|
|
303
|
+
archive = await loadDocxArchive(bytes, options.archive);
|
|
304
|
+
report.checks.set("archive-safety", "passed");
|
|
305
|
+
} catch (error) {
|
|
306
|
+
report.checks.set("archive-safety", "failed");
|
|
307
|
+
addIssue(report, {
|
|
308
|
+
check: "archive-safety",
|
|
309
|
+
code: error instanceof DocxArchiveError ? `archive-${error.reason}` : "archive-load-failed",
|
|
310
|
+
message: "The DOCX package could not be loaded within the configured safety limits.",
|
|
311
|
+
severity: "error"
|
|
312
|
+
});
|
|
313
|
+
return finishReport(report);
|
|
314
|
+
}
|
|
315
|
+
if (!validateRequiredParts(archive, report)) return finishReport(report);
|
|
316
|
+
let xml;
|
|
317
|
+
try {
|
|
318
|
+
xml = await validateXmlParts(archive, report);
|
|
319
|
+
} catch (error) {
|
|
320
|
+
report.checks.set("xml-well-formedness", "indeterminate");
|
|
321
|
+
addIssue(report, {
|
|
322
|
+
check: "xml-well-formedness",
|
|
323
|
+
code: error instanceof DocxArchiveError ? `archive-${error.reason}` : "xml-read-failed",
|
|
324
|
+
message: "The XML package parts could not be read within the configured safety limits.",
|
|
325
|
+
severity: "warning"
|
|
326
|
+
});
|
|
327
|
+
return finishReport(report);
|
|
328
|
+
}
|
|
329
|
+
if (xml === null) return finishReport(report);
|
|
330
|
+
if (!validatePackageRoots(xml, report)) return finishReport(report);
|
|
331
|
+
validateConformanceClass(xml.documentXml, report);
|
|
332
|
+
await validateCanonicalModel(bytes, report);
|
|
333
|
+
return finishReport(report);
|
|
334
|
+
};
|
|
335
|
+
//#endregion
|
|
336
|
+
export { FOLIO_DOCX_CONFORMANCE_CHECKS, FOLIO_DOCX_CONFORMANCE_ISSUE_CODES, FOLIO_DOCX_CONFORMANCE_PROFILE, FOLIO_DOCX_CONFORMANCE_REPORT_VERSION, validateDocxConformance };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { findChild, getAttribute, parseBooleanElement, parseXmlDocument } from "./xmlParser.js";
|
|
1
|
+
import { findChild, findChildren, getAttribute, parseBooleanElement, parseXmlDocument } from "./xmlParser.js";
|
|
2
2
|
//#region src/docx/settingsParser.ts
|
|
3
3
|
/** OOXML default per §17.6.13 when `w:defaultTabStop` is absent. */
|
|
4
4
|
const DEFAULT_TAB_STOP_TWIPS = 720;
|
|
@@ -35,6 +35,8 @@ function parseSettings(xml) {
|
|
|
35
35
|
const noLineBreaksBefore = parseKinsokuOverride(root, "noLineBreaksBefore");
|
|
36
36
|
const noLineBreaksAfter = parseKinsokuOverride(root, "noLineBreaksAfter");
|
|
37
37
|
const compat = root ? findChild(root, "w", "compat") : null;
|
|
38
|
+
const compatibilityMode = parseCompatibilityMode(compat);
|
|
39
|
+
if (compatibilityMode !== void 0) settings.compatibilityMode = compatibilityMode;
|
|
38
40
|
const splitPageBreakAndParagraphMark = compat ? findChild(compat, "w", "splitPgBreakAndParaMark") : null;
|
|
39
41
|
if (splitPageBreakAndParagraphMark && parseBooleanElement(splitPageBreakAndParagraphMark)) settings.splitPageBreakAndParagraphMark = true;
|
|
40
42
|
const applyBreakingRules = compat ? findChild(compat, "w", "applyBreakingRules") : null;
|
|
@@ -46,6 +48,14 @@ function parseSettings(xml) {
|
|
|
46
48
|
};
|
|
47
49
|
return settings;
|
|
48
50
|
}
|
|
51
|
+
function parseCompatibilityMode(compat) {
|
|
52
|
+
if (!compat) return;
|
|
53
|
+
const setting = findChildren(compat, "w", "compatSetting").find((candidate) => getAttribute(candidate, "w", "name") === "compatibilityMode");
|
|
54
|
+
const raw = setting ? getAttribute(setting, "w", "val") : null;
|
|
55
|
+
if (raw === null || !/^\d+$/u.test(raw)) return;
|
|
56
|
+
const parsed = Number.parseInt(raw, 10);
|
|
57
|
+
return Number.isSafeInteger(parsed) ? parsed : void 0;
|
|
58
|
+
}
|
|
49
59
|
function parseOnOffSetting(root, name) {
|
|
50
60
|
if (!root) return;
|
|
51
61
|
const element = findChild(root, "w", name);
|
package/dist/docx/shapeParser.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { findAllDeep, findChildByLocalName, findChildrenByLocalName, getAttribute, parseNumericAttribute } from "./xmlParser.js";
|
|
2
1
|
import { ShapeOutlineStyleSchema, ShapeTypeSchema, narrowEnum } from "./parserEnums.js";
|
|
2
|
+
import { findAllDeep, findChildByLocalName, findChildrenByLocalName, getAttribute, parseNumericAttribute } from "./xmlParser.js";
|
|
3
3
|
import { parseAnchorPosition, parseAnchorWrap, parseColorElement, parseFill } from "./drawingUtils.js";
|
|
4
4
|
//#region src/docx/shapeParser.ts
|
|
5
5
|
/** Convert OOXML rotation (1/60000ths of a degree) to degrees. */
|
package/dist/docx/styleParser.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { findChild, findChildren, getAttribute, getLocalName, parseBooleanElement, parseNumericAttribute, parseTableMeasurementValue, parseXmlDocument } from "./xmlParser.js";
|
|
2
1
|
import { BorderStyleSchema, ConditionalStyleTypeSchema, EmphasisMarkSchema, FontThemeSchema, HighlightColorSchema, LineSpacingRuleSchema, ParagraphAlignmentSchema, ShadingPatternSchema, StyleTypeSchema, TabLeaderSchema, TabStopAlignmentSchema, TableCellTextDirectionSchema, TableRowHeightRuleSchema, TableWidthTypeSchema, TextEffectSchema, ThemeColorSlotSchema, UnderlineStyleSchema, narrowEnum } from "./parserEnums.js";
|
|
3
|
-
import {
|
|
2
|
+
import { findChild, findChildren, getAttribute, getLocalName, parseBooleanElement, parseNumericAttribute, parseTableMeasurementValue, parseXmlDocument } from "./xmlParser.js";
|
|
4
3
|
import { mergeTextFormatting } from "../utils/textFormattingMerge.js";
|
|
5
4
|
import { mergeParagraphFormatting } from "../utils/paragraphFormattingMerge.js";
|
|
5
|
+
import { resolveThemeFontRef } from "./themeParser.js";
|
|
6
6
|
//#region src/docx/styleParser.ts
|
|
7
7
|
/**
|
|
8
8
|
* Parse text formatting properties (w:rPr)
|
|
@@ -263,6 +263,8 @@ function parseParagraphProperties(pPr, theme) {
|
|
|
263
263
|
}
|
|
264
264
|
const bidi = findChild(pPr, "w", "bidi");
|
|
265
265
|
if (bidi) formatting.bidi = parseBooleanElement(bidi);
|
|
266
|
+
const snapToGrid = findChild(pPr, "w", "snapToGrid");
|
|
267
|
+
if (snapToGrid) formatting.snapToGrid = parseBooleanElement(snapToGrid);
|
|
266
268
|
const spacing = findChild(pPr, "w", "spacing");
|
|
267
269
|
if (spacing) {
|
|
268
270
|
const before = parseNumericAttribute(spacing, "w", "before");
|
package/dist/docx/tableParser.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { BorderStyleSchema, FloatingTableXSpecSchema, FloatingTableYSpecSchema, ShadingPatternSchema, TableCellTextDirectionSchema, ThemeColorSlotSchema, narrowEnum } from "./parserEnums.js";
|
|
1
2
|
import { findChild, findChildByLocalName, findChildren, getAttribute, getChildElements, mergeXmlnsDeclarations, parseBooleanElement, parseNumericAttribute, parseTableMeasurementValue } from "./xmlParser.js";
|
|
2
3
|
import { parseBookmarkEnd, parseBookmarkStart } from "./bookmarkParser.js";
|
|
3
|
-
import { BorderStyleSchema, FloatingTableXSpecSchema, FloatingTableYSpecSchema, ShadingPatternSchema, TableCellTextDirectionSchema, ThemeColorSlotSchema, narrowEnum } from "./parserEnums.js";
|
|
4
4
|
import { parseParagraph } from "./paragraphParser.js";
|
|
5
5
|
import { appendBookmarkMarkerToLastParagraphInBlocks, appendBookmarkMarkerToLastParagraphInCells, prependBookmarkMarkersToFirstParagraphInBlocks, prependBookmarkMarkersToFirstParagraphInCell } from "./bookmarkPlacement.js";
|
|
6
6
|
import { enrichParagraphTextBoxes } from "./paragraphTextBoxEnrichment.js";
|
|
@@ -349,10 +349,20 @@ function parseTableCellStructuralChange(tcPrElement) {
|
|
|
349
349
|
info: parseTrackedChangeInfo(deletion)
|
|
350
350
|
};
|
|
351
351
|
const merge = findChild(tcPrElement, "w", "cellMerge");
|
|
352
|
-
if (merge)
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
352
|
+
if (merge) {
|
|
353
|
+
const verticalMerge = parseTableCellVerticalMergeRevisionValue(getAttribute(merge, "w", "vMerge"));
|
|
354
|
+
const verticalMergeOriginal = parseTableCellVerticalMergeRevisionValue(getAttribute(merge, "w", "vMergeOrig"));
|
|
355
|
+
return {
|
|
356
|
+
type: "tableCellMerge",
|
|
357
|
+
info: parseTrackedChangeInfo(merge),
|
|
358
|
+
...verticalMerge !== void 0 ? { verticalMerge } : {},
|
|
359
|
+
...verticalMergeOriginal !== void 0 ? { verticalMergeOriginal } : {}
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
function parseTableCellVerticalMergeRevisionValue(value) {
|
|
364
|
+
if (value === "cont") return "continue";
|
|
365
|
+
if (value === "rest") return "rest";
|
|
356
366
|
}
|
|
357
367
|
/**
|
|
358
368
|
* Parse table row properties (w:trPr)
|
|
@@ -482,7 +492,8 @@ function parseTableCellProperties(tcPrElement) {
|
|
|
482
492
|
else formatting.vMerge = "continue";
|
|
483
493
|
if (parseBooleanElement(findChild(tcPrElement, "w", "tcFitText"))) formatting.fitText = true;
|
|
484
494
|
if (parseBooleanElement(findChild(tcPrElement, "w", "noWrap"))) formatting.noWrap = true;
|
|
485
|
-
|
|
495
|
+
const hideMark = parseBooleanElement(findChild(tcPrElement, "w", "hideMark"));
|
|
496
|
+
if (hideMark !== void 0) formatting.hideMark = hideMark;
|
|
486
497
|
const conditionalFormat = parseConditionalFormatStyle(findChild(tcPrElement, "w", "cnfStyle"));
|
|
487
498
|
if (conditionalFormat) formatting.conditionalFormat = conditionalFormat;
|
|
488
499
|
if (Object.keys(formatting).length === 0) return;
|
|
@@ -508,7 +519,7 @@ function parseCellContent(tcElement, styles, theme, numbering, rels, media, opti
|
|
|
508
519
|
const localName = getLocalName(child.name);
|
|
509
520
|
if (localName === "p") {
|
|
510
521
|
const para = parseParagraph(child, styles, theme, numbering, rels, media, options);
|
|
511
|
-
enrichParagraphTextBoxes(para, child, styles, theme, numbering, rels, media);
|
|
522
|
+
enrichParagraphTextBoxes(para, child, styles, theme, numbering, rels, media, parseTable);
|
|
512
523
|
prependPendingBookmarkMarkers(para, pendingBookmarkMarkers);
|
|
513
524
|
content.push(para);
|
|
514
525
|
} else if (localName === "tbl") {
|
|
@@ -15,16 +15,16 @@ declare function extractTextBoxContentElements(txbxContent: XmlElement | null):
|
|
|
15
15
|
/**
|
|
16
16
|
* Type for the paragraph parser function to avoid circular imports
|
|
17
17
|
*/
|
|
18
|
-
type ParagraphParserFn = (node: XmlElement, styles: StyleMap | null, theme: document_d_exports.Theme | null, numbering: NumberingMap | null, rels
|
|
18
|
+
type ParagraphParserFn = (node: XmlElement, styles: StyleMap | null, theme: document_d_exports.Theme | null, numbering: NumberingMap | null, rels: document_d_exports.RelationshipMap | null, media: Map<string, document_d_exports.MediaFile> | null) => document_d_exports.Paragraph;
|
|
19
19
|
/**
|
|
20
20
|
* Type for the table parser function to avoid circular imports
|
|
21
21
|
*/
|
|
22
|
-
type TableParserFn = (node: XmlElement, styles: StyleMap | null, theme: document_d_exports.Theme | null, numbering: NumberingMap | null, rels
|
|
22
|
+
type TableParserFn = (node: XmlElement, styles: StyleMap | null, theme: document_d_exports.Theme | null, numbering: NumberingMap | null, rels: document_d_exports.RelationshipMap | null, media: Map<string, document_d_exports.MediaFile> | null) => document_d_exports.Table;
|
|
23
23
|
/**
|
|
24
24
|
* Parse text box content with provided parser functions
|
|
25
25
|
* This avoids circular dependencies by accepting parser functions as parameters
|
|
26
26
|
*/
|
|
27
|
-
declare function parseTextBoxContent(txbxContent: XmlElement | null, parseParagraph: ParagraphParserFn, parseTable: TableParserFn | null, styles: StyleMap | null, theme: document_d_exports.Theme | null, numbering: NumberingMap | null, rels
|
|
27
|
+
declare function parseTextBoxContent(txbxContent: XmlElement | null, parseParagraph: ParagraphParserFn, parseTable: TableParserFn | null, styles: StyleMap | null, theme: document_d_exports.Theme | null, numbering: NumberingMap | null, rels: document_d_exports.RelationshipMap | null, media: Map<string, document_d_exports.MediaFile> | null): (document_d_exports.Paragraph | document_d_exports.Table)[];
|
|
28
28
|
/**
|
|
29
29
|
* Check if a drawing element contains a text box
|
|
30
30
|
* Text boxes are shapes with wps:txbx content
|
|
@@ -44,20 +44,20 @@ function extractTextBoxContentElements(txbxContent) {
|
|
|
44
44
|
* Parse text box content with provided parser functions
|
|
45
45
|
* This avoids circular dependencies by accepting parser functions as parameters
|
|
46
46
|
*/
|
|
47
|
-
function parseTextBoxContent(txbxContent, parseParagraph, parseTable, styles, theme, numbering, rels,
|
|
47
|
+
function parseTextBoxContent(txbxContent, parseParagraph, parseTable, styles, theme, numbering, rels, media) {
|
|
48
48
|
if (!txbxContent) return [];
|
|
49
|
-
const
|
|
49
|
+
const blocks = [];
|
|
50
50
|
const children = getChildElements(txbxContent);
|
|
51
51
|
for (const child of children) {
|
|
52
52
|
const name = child.name || "";
|
|
53
53
|
const colonIdx = name.indexOf(":");
|
|
54
54
|
const localName = colonIdx !== -1 ? name.slice(colonIdx + 1) : name;
|
|
55
55
|
if (localName === "p") {
|
|
56
|
-
const paragraph = parseParagraph(child, styles, theme, numbering, rels);
|
|
57
|
-
|
|
58
|
-
} else if (localName === "tbl" && parseTable)
|
|
56
|
+
const paragraph = parseParagraph(child, styles, theme, numbering, rels, media);
|
|
57
|
+
blocks.push(paragraph);
|
|
58
|
+
} else if (localName === "tbl" && parseTable) blocks.push(parseTable(child, styles, theme, numbering, rels, media));
|
|
59
59
|
}
|
|
60
|
-
return
|
|
60
|
+
return blocks;
|
|
61
61
|
}
|
|
62
62
|
/**
|
|
63
63
|
* Check if a drawing element contains a text box
|
|
@@ -232,16 +232,20 @@ function hasTextBoxContent(textBox) {
|
|
|
232
232
|
* Get plain text from text box (helper for search/indexing)
|
|
233
233
|
*/
|
|
234
234
|
function getTextBoxText(textBox) {
|
|
235
|
-
|
|
236
|
-
|
|
235
|
+
return textBox.content.map(getTextBoxBlockText).join("\n");
|
|
236
|
+
}
|
|
237
|
+
const getTextBoxBlockText = (block) => {
|
|
238
|
+
if (block.type === "paragraph") {
|
|
237
239
|
const runTexts = [];
|
|
238
|
-
for (const item of
|
|
240
|
+
for (const item of block.content) {
|
|
241
|
+
if (item.type !== "run") continue;
|
|
239
242
|
for (const content of item.content) if (content.type === "text") runTexts.push(content.text);
|
|
240
243
|
}
|
|
241
|
-
|
|
244
|
+
return runTexts.join("");
|
|
242
245
|
}
|
|
243
|
-
return
|
|
244
|
-
|
|
246
|
+
if (block.type === "table") return block.rows.map((row) => row.cells.map((cell) => cell.content.map(getTextBoxBlockText).join("\n")).join(" ")).join("\n");
|
|
247
|
+
return block.content.map(getTextBoxBlockText).join("\n");
|
|
248
|
+
};
|
|
245
249
|
/**
|
|
246
250
|
* Resolve fill color to CSS color string
|
|
247
251
|
*/
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { XMLValidator } from "fast-xml-parser";
|
|
2
|
+
//#region src/docx/xmlSafety.ts
|
|
3
|
+
const DOCTYPE_PATTERN = /<!DOCTYPE(?:\s|>)/iu;
|
|
4
|
+
const getDocxXmlSafetyIssue = (xml) => {
|
|
5
|
+
if (DOCTYPE_PATTERN.test(xml)) return "doctype-forbidden";
|
|
6
|
+
return XMLValidator.validate(xml) === true ? null : "not-well-formed";
|
|
7
|
+
};
|
|
8
|
+
//#endregion
|
|
9
|
+
export { getDocxXmlSafetyIssue };
|
|
@@ -196,9 +196,7 @@ function collectFieldRuns(block, out) {
|
|
|
196
196
|
if (block.kind === "paragraph") {
|
|
197
197
|
for (const run of block.runs) if (run.kind === "field") out.push(run);
|
|
198
198
|
} else if (block.kind === "table") for (const row of block.rows) for (const cell of row.cells) for (const cellBlock of cell.blocks) collectFieldRuns(cellBlock, out);
|
|
199
|
-
else if (block.kind === "textBox")
|
|
200
|
-
for (const paragraph of block.content) for (const run of paragraph.runs) if (run.kind === "field") out.push(run);
|
|
201
|
-
}
|
|
199
|
+
else if (block.kind === "textBox") for (const child of block.content) collectFieldRuns(child, out);
|
|
202
200
|
}
|
|
203
201
|
//#endregion
|
|
204
202
|
export { buildHeaderFooterFieldValues, fieldValuesEqual, resolveFieldValues };
|