@usejunior/docx-core 0.9.1 → 0.11.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/LICENSE +202 -21
- package/NOTICE +2 -0
- package/README.md +2 -2
- package/dist/.tsbuildinfo +1 -1
- package/dist/atomizer.d.ts +28 -8
- package/dist/atomizer.d.ts.map +1 -1
- package/dist/atomizer.js +96 -25
- package/dist/atomizer.js.map +1 -1
- package/dist/baselines/atomizer/auxiliaryIdCollision.d.ts +99 -0
- package/dist/baselines/atomizer/auxiliaryIdCollision.d.ts.map +1 -0
- package/dist/baselines/atomizer/auxiliaryIdCollision.js +415 -0
- package/dist/baselines/atomizer/auxiliaryIdCollision.js.map +1 -0
- package/dist/baselines/atomizer/documentReconstructor.d.ts.map +1 -1
- package/dist/baselines/atomizer/documentReconstructor.js +333 -112
- package/dist/baselines/atomizer/documentReconstructor.js.map +1 -1
- package/dist/baselines/atomizer/formattingFidelity.d.ts +99 -0
- package/dist/baselines/atomizer/formattingFidelity.d.ts.map +1 -0
- package/dist/baselines/atomizer/formattingFidelity.js +449 -0
- package/dist/baselines/atomizer/formattingFidelity.js.map +1 -0
- package/dist/baselines/atomizer/inPlaceModifier-bookmarks.d.ts +37 -0
- package/dist/baselines/atomizer/inPlaceModifier-bookmarks.d.ts.map +1 -0
- package/dist/baselines/atomizer/inPlaceModifier-bookmarks.js +189 -0
- package/dist/baselines/atomizer/inPlaceModifier-bookmarks.js.map +1 -0
- package/dist/baselines/atomizer/inPlaceModifier-containers.d.ts +74 -0
- package/dist/baselines/atomizer/inPlaceModifier-containers.d.ts.map +1 -0
- package/dist/baselines/atomizer/inPlaceModifier-containers.js +171 -0
- package/dist/baselines/atomizer/inPlaceModifier-containers.js.map +1 -0
- package/dist/baselines/atomizer/inPlaceModifier-deletion.d.ts +88 -0
- package/dist/baselines/atomizer/inPlaceModifier-deletion.d.ts.map +1 -0
- package/dist/baselines/atomizer/inPlaceModifier-deletion.js +326 -0
- package/dist/baselines/atomizer/inPlaceModifier-deletion.js.map +1 -0
- package/dist/baselines/atomizer/inPlaceModifier-postprocess.d.ts +85 -0
- package/dist/baselines/atomizer/inPlaceModifier-postprocess.d.ts.map +1 -0
- package/dist/baselines/atomizer/inPlaceModifier-postprocess.js +402 -0
- package/dist/baselines/atomizer/inPlaceModifier-postprocess.js.map +1 -0
- package/dist/baselines/atomizer/inPlaceModifier-presplit.d.ts +39 -0
- package/dist/baselines/atomizer/inPlaceModifier-presplit.d.ts.map +1 -0
- package/dist/baselines/atomizer/inPlaceModifier-presplit.js +265 -0
- package/dist/baselines/atomizer/inPlaceModifier-presplit.js.map +1 -0
- package/dist/baselines/atomizer/inPlaceModifier-shared.d.ts +62 -0
- package/dist/baselines/atomizer/inPlaceModifier-shared.d.ts.map +1 -0
- package/dist/baselines/atomizer/inPlaceModifier-shared.js +139 -0
- package/dist/baselines/atomizer/inPlaceModifier-shared.js.map +1 -0
- package/dist/baselines/atomizer/inPlaceModifier-wrappers.d.ts +198 -0
- package/dist/baselines/atomizer/inPlaceModifier-wrappers.d.ts.map +1 -0
- package/dist/baselines/atomizer/inPlaceModifier-wrappers.js +475 -0
- package/dist/baselines/atomizer/inPlaceModifier-wrappers.js.map +1 -0
- package/dist/baselines/atomizer/inPlaceModifier.d.ts +6 -290
- package/dist/baselines/atomizer/inPlaceModifier.d.ts.map +1 -1
- package/dist/baselines/atomizer/inPlaceModifier.js +23 -1828
- package/dist/baselines/atomizer/inPlaceModifier.js.map +1 -1
- package/dist/baselines/atomizer/pipeline.d.ts +36 -2
- package/dist/baselines/atomizer/pipeline.d.ts.map +1 -1
- package/dist/baselines/atomizer/pipeline.js +216 -144
- package/dist/baselines/atomizer/pipeline.js.map +1 -1
- package/dist/baselines/atomizer/trackChangesAcceptorAst.d.ts.map +1 -1
- package/dist/baselines/atomizer/trackChangesAcceptorAst.js +199 -173
- package/dist/baselines/atomizer/trackChangesAcceptorAst.js.map +1 -1
- package/dist/baselines/wmlcomparer/DotnetCli.d.ts.map +1 -1
- package/dist/baselines/wmlcomparer/DotnetCli.js +7 -0
- package/dist/baselines/wmlcomparer/DotnetCli.js.map +1 -1
- package/dist/cli/compare-two.d.ts.map +1 -1
- package/dist/cli/compare-two.js +3 -1
- package/dist/cli/compare-two.js.map +1 -1
- package/dist/cli/conformance-adapter.d.ts +3 -0
- package/dist/cli/conformance-adapter.d.ts.map +1 -0
- package/dist/cli/conformance-adapter.js +93 -0
- package/dist/cli/conformance-adapter.js.map +1 -0
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +5 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/compare-types.d.ts +197 -0
- package/dist/compare-types.d.ts.map +1 -0
- package/dist/compare-types.js +2 -0
- package/dist/compare-types.js.map +1 -0
- package/dist/core-types.d.ts +5 -1
- package/dist/core-types.d.ts.map +1 -1
- package/dist/core-types.js +5 -1
- package/dist/core-types.js.map +1 -1
- package/dist/footnotes.d.ts +8 -3
- package/dist/footnotes.d.ts.map +1 -1
- package/dist/footnotes.js +8 -3
- package/dist/footnotes.js.map +1 -1
- package/dist/generation/compile.d.ts +21 -0
- package/dist/generation/compile.d.ts.map +1 -0
- package/dist/generation/compile.js +46 -0
- package/dist/generation/compile.js.map +1 -0
- package/dist/generation/context.d.ts +42 -0
- package/dist/generation/context.d.ts.map +1 -0
- package/dist/generation/context.js +65 -0
- package/dist/generation/context.js.map +1 -0
- package/dist/generation/emit/comments-part.d.ts +36 -0
- package/dist/generation/emit/comments-part.d.ts.map +1 -0
- package/dist/generation/emit/comments-part.js +116 -0
- package/dist/generation/emit/comments-part.js.map +1 -0
- package/dist/generation/emit/document-part.d.ts +24 -0
- package/dist/generation/emit/document-part.d.ts.map +1 -0
- package/dist/generation/emit/document-part.js +60 -0
- package/dist/generation/emit/document-part.js.map +1 -0
- package/dist/generation/emit/emit-context.d.ts +26 -0
- package/dist/generation/emit/emit-context.d.ts.map +1 -0
- package/dist/generation/emit/emit-context.js +19 -0
- package/dist/generation/emit/emit-context.js.map +1 -0
- package/dist/generation/emit/header-footer-part.d.ts +23 -0
- package/dist/generation/emit/header-footer-part.d.ts.map +1 -0
- package/dist/generation/emit/header-footer-part.js +57 -0
- package/dist/generation/emit/header-footer-part.js.map +1 -0
- package/dist/generation/emit/numbering-part.d.ts +29 -0
- package/dist/generation/emit/numbering-part.d.ts.map +1 -0
- package/dist/generation/emit/numbering-part.js +100 -0
- package/dist/generation/emit/numbering-part.js.map +1 -0
- package/dist/generation/emit/package-parts.d.ts +24 -0
- package/dist/generation/emit/package-parts.d.ts.map +1 -0
- package/dist/generation/emit/package-parts.js +121 -0
- package/dist/generation/emit/package-parts.js.map +1 -0
- package/dist/generation/emit/paragraph.d.ts +24 -0
- package/dist/generation/emit/paragraph.d.ts.map +1 -0
- package/dist/generation/emit/paragraph.js +63 -0
- package/dist/generation/emit/paragraph.js.map +1 -0
- package/dist/generation/emit/properties.d.ts +34 -0
- package/dist/generation/emit/properties.d.ts.map +1 -0
- package/dist/generation/emit/properties.js +138 -0
- package/dist/generation/emit/properties.js.map +1 -0
- package/dist/generation/emit/run.d.ts +15 -0
- package/dist/generation/emit/run.d.ts.map +1 -0
- package/dist/generation/emit/run.js +71 -0
- package/dist/generation/emit/run.js.map +1 -0
- package/dist/generation/emit/section.d.ts +29 -0
- package/dist/generation/emit/section.d.ts.map +1 -0
- package/dist/generation/emit/section.js +117 -0
- package/dist/generation/emit/section.js.map +1 -0
- package/dist/generation/emit/settings-part.d.ts +13 -0
- package/dist/generation/emit/settings-part.d.ts.map +1 -0
- package/dist/generation/emit/settings-part.js +24 -0
- package/dist/generation/emit/settings-part.js.map +1 -0
- package/dist/generation/emit/styles-part.d.ts +16 -0
- package/dist/generation/emit/styles-part.d.ts.map +1 -0
- package/dist/generation/emit/styles-part.js +80 -0
- package/dist/generation/emit/styles-part.js.map +1 -0
- package/dist/generation/emit/table.d.ts +26 -0
- package/dist/generation/emit/table.d.ts.map +1 -0
- package/dist/generation/emit/table.js +196 -0
- package/dist/generation/emit/table.js.map +1 -0
- package/dist/generation/errors.d.ts +22 -0
- package/dist/generation/errors.d.ts.map +1 -0
- package/dist/generation/errors.js +29 -0
- package/dist/generation/errors.js.map +1 -0
- package/dist/generation/index.d.ts +13 -0
- package/dist/generation/index.d.ts.map +1 -0
- package/dist/generation/index.js +12 -0
- package/dist/generation/index.js.map +1 -0
- package/dist/generation/ordering.d.ts +46 -0
- package/dist/generation/ordering.d.ts.map +1 -0
- package/dist/generation/ordering.js +119 -0
- package/dist/generation/ordering.js.map +1 -0
- package/dist/generation/recipes.d.ts +47 -0
- package/dist/generation/recipes.d.ts.map +1 -0
- package/dist/generation/recipes.js +84 -0
- package/dist/generation/recipes.js.map +1 -0
- package/dist/generation/structural-checks.d.ts +24 -0
- package/dist/generation/structural-checks.d.ts.map +1 -0
- package/dist/generation/structural-checks.js +318 -0
- package/dist/generation/structural-checks.js.map +1 -0
- package/dist/generation/types.d.ts +217 -0
- package/dist/generation/types.d.ts.map +1 -0
- package/dist/generation/types.js +16 -0
- package/dist/generation/types.js.map +1 -0
- package/dist/generation/validate-spec.d.ts +27 -0
- package/dist/generation/validate-spec.d.ts.map +1 -0
- package/dist/generation/validate-spec.js +307 -0
- package/dist/generation/validate-spec.js.map +1 -0
- package/dist/index.d.ts +9 -150
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -1
- package/dist/integration/generation-probes.d.ts +15 -0
- package/dist/integration/generation-probes.d.ts.map +1 -0
- package/dist/integration/generation-probes.js +84 -0
- package/dist/integration/generation-probes.js.map +1 -0
- package/dist/integration/libreoffice-oracle.d.ts +49 -0
- package/dist/integration/libreoffice-oracle.d.ts.map +1 -0
- package/dist/integration/libreoffice-oracle.js +290 -0
- package/dist/integration/libreoffice-oracle.js.map +1 -0
- package/dist/integration/synthetic-docx-fixture.d.ts +72 -0
- package/dist/integration/synthetic-docx-fixture.d.ts.map +1 -1
- package/dist/integration/synthetic-docx-fixture.js +131 -4
- package/dist/integration/synthetic-docx-fixture.js.map +1 -1
- package/dist/primitives/accept_changes.d.ts +4 -3
- package/dist/primitives/accept_changes.d.ts.map +1 -1
- package/dist/primitives/accept_changes.js +163 -77
- package/dist/primitives/accept_changes.js.map +1 -1
- package/dist/primitives/comments.d.ts +12 -3
- package/dist/primitives/comments.d.ts.map +1 -1
- package/dist/primitives/comments.js +374 -97
- package/dist/primitives/comments.js.map +1 -1
- package/dist/primitives/content_fingerprint.d.ts +29 -0
- package/dist/primitives/content_fingerprint.d.ts.map +1 -0
- package/dist/primitives/content_fingerprint.js +63 -0
- package/dist/primitives/content_fingerprint.js.map +1 -0
- package/dist/primitives/document.d.ts +94 -15
- package/dist/primitives/document.d.ts.map +1 -1
- package/dist/primitives/document.js +373 -36
- package/dist/primitives/document.js.map +1 -1
- package/dist/primitives/document_view-comments.d.ts +18 -0
- package/dist/primitives/document_view-comments.d.ts.map +1 -0
- package/dist/primitives/document_view-comments.js +160 -0
- package/dist/primitives/document_view-comments.js.map +1 -0
- package/dist/primitives/document_view-headings.d.ts +45 -0
- package/dist/primitives/document_view-headings.d.ts.map +1 -0
- package/dist/primitives/document_view-headings.js +247 -0
- package/dist/primitives/document_view-headings.js.map +1 -0
- package/dist/primitives/document_view-styles.d.ts +11 -0
- package/dist/primitives/document_view-styles.d.ts.map +1 -0
- package/dist/primitives/document_view-styles.js +104 -0
- package/dist/primitives/document_view-styles.js.map +1 -0
- package/dist/primitives/document_view-toon.d.ts +37 -0
- package/dist/primitives/document_view-toon.d.ts.map +1 -0
- package/dist/primitives/document_view-toon.js +199 -0
- package/dist/primitives/document_view-toon.js.map +1 -0
- package/dist/primitives/document_view-types.d.ts +152 -0
- package/dist/primitives/document_view-types.d.ts.map +1 -0
- package/dist/primitives/document_view-types.js +2 -0
- package/dist/primitives/document_view-types.js.map +1 -0
- package/dist/primitives/document_view.d.ts +8 -106
- package/dist/primitives/document_view.d.ts.map +1 -1
- package/dist/primitives/document_view.js +153 -312
- package/dist/primitives/document_view.js.map +1 -1
- package/dist/primitives/dom-helpers.d.ts +9 -0
- package/dist/primitives/dom-helpers.d.ts.map +1 -1
- package/dist/primitives/dom-helpers.js +10 -1
- package/dist/primitives/dom-helpers.js.map +1 -1
- package/dist/primitives/footnotes.d.ts +4 -3
- package/dist/primitives/footnotes.d.ts.map +1 -1
- package/dist/primitives/footnotes.js +232 -44
- package/dist/primitives/footnotes.js.map +1 -1
- package/dist/primitives/formatting_tags.d.ts +7 -0
- package/dist/primitives/formatting_tags.d.ts.map +1 -1
- package/dist/primitives/formatting_tags.js +22 -11
- package/dist/primitives/formatting_tags.js.map +1 -1
- package/dist/primitives/index.d.ts +10 -0
- package/dist/primitives/index.d.ts.map +1 -1
- package/dist/primitives/index.js +9 -0
- package/dist/primitives/index.js.map +1 -1
- package/dist/primitives/layout.d.ts +4 -3
- package/dist/primitives/layout.d.ts.map +1 -1
- package/dist/primitives/layout.js +45 -3
- package/dist/primitives/layout.js.map +1 -1
- package/dist/primitives/merge_runs.d.ts +21 -3
- package/dist/primitives/merge_runs.d.ts.map +1 -1
- package/dist/primitives/merge_runs.js +32 -10
- package/dist/primitives/merge_runs.js.map +1 -1
- package/dist/primitives/minimal_save.d.ts +38 -0
- package/dist/primitives/minimal_save.d.ts.map +1 -0
- package/dist/primitives/minimal_save.js +323 -0
- package/dist/primitives/minimal_save.js.map +1 -0
- package/dist/primitives/namespaces.d.ts +47 -0
- package/dist/primitives/namespaces.d.ts.map +1 -1
- package/dist/primitives/namespaces.js +52 -0
- package/dist/primitives/namespaces.js.map +1 -1
- package/dist/primitives/reject_changes.d.ts +6 -4
- package/dist/primitives/reject_changes.d.ts.map +1 -1
- package/dist/primitives/reject_changes.js +187 -91
- package/dist/primitives/reject_changes.js.map +1 -1
- package/dist/primitives/revision-parts.d.ts +7 -0
- package/dist/primitives/revision-parts.d.ts.map +1 -0
- package/dist/primitives/revision-parts.js +27 -0
- package/dist/primitives/revision-parts.js.map +1 -0
- package/dist/primitives/revision-vocabulary.d.ts +7 -0
- package/dist/primitives/revision-vocabulary.d.ts.map +1 -0
- package/dist/primitives/revision-vocabulary.js +39 -0
- package/dist/primitives/revision-vocabulary.js.map +1 -0
- package/dist/primitives/schema-corpus-capture.d.ts +19 -0
- package/dist/primitives/schema-corpus-capture.d.ts.map +1 -0
- package/dist/primitives/schema-corpus-capture.js +29 -0
- package/dist/primitives/schema-corpus-capture.js.map +1 -0
- package/dist/primitives/sectPrAudit.d.ts +19 -0
- package/dist/primitives/sectPrAudit.d.ts.map +1 -0
- package/dist/primitives/sectPrAudit.js +165 -0
- package/dist/primitives/sectPrAudit.js.map +1 -0
- package/dist/primitives/semantic_tags.d.ts +7 -0
- package/dist/primitives/semantic_tags.d.ts.map +1 -1
- package/dist/primitives/semantic_tags.js +23 -4
- package/dist/primitives/semantic_tags.js.map +1 -1
- package/dist/primitives/serialize_html.d.ts +37 -0
- package/dist/primitives/serialize_html.d.ts.map +1 -0
- package/dist/primitives/serialize_html.js +395 -0
- package/dist/primitives/serialize_html.js.map +1 -0
- package/dist/primitives/serialize_markdown.d.ts +16 -0
- package/dist/primitives/serialize_markdown.d.ts.map +1 -0
- package/dist/primitives/serialize_markdown.js +300 -0
- package/dist/primitives/serialize_markdown.js.map +1 -0
- package/dist/primitives/serialize_plaintext.d.ts +15 -0
- package/dist/primitives/serialize_plaintext.d.ts.map +1 -0
- package/dist/primitives/serialize_plaintext.js +154 -0
- package/dist/primitives/serialize_plaintext.js.map +1 -0
- package/dist/primitives/styles.d.ts +15 -0
- package/dist/primitives/styles.d.ts.map +1 -1
- package/dist/primitives/styles.js +33 -22
- package/dist/primitives/styles.js.map +1 -1
- package/dist/primitives/tables.d.ts.map +1 -1
- package/dist/primitives/tables.js +13 -3
- package/dist/primitives/tables.js.map +1 -1
- package/dist/primitives/text.d.ts +2 -1
- package/dist/primitives/text.d.ts.map +1 -1
- package/dist/primitives/text.js +116 -12
- package/dist/primitives/text.js.map +1 -1
- package/dist/primitives/track-changes-emitter.d.ts +148 -0
- package/dist/primitives/track-changes-emitter.d.ts.map +1 -0
- package/dist/primitives/track-changes-emitter.js +291 -0
- package/dist/primitives/track-changes-emitter.js.map +1 -0
- package/dist/primitives/validate_ai_revisions.d.ts +35 -0
- package/dist/primitives/validate_ai_revisions.d.ts.map +1 -0
- package/dist/primitives/validate_ai_revisions.js +323 -0
- package/dist/primitives/validate_ai_revisions.js.map +1 -0
- package/dist/primitives/xml-helpers.d.ts +29 -0
- package/dist/primitives/xml-helpers.d.ts.map +1 -0
- package/dist/primitives/xml-helpers.js +35 -0
- package/dist/primitives/xml-helpers.js.map +1 -0
- package/dist/primitives/xml.d.ts +5 -0
- package/dist/primitives/xml.d.ts.map +1 -1
- package/dist/primitives/xml.js +5 -0
- package/dist/primitives/xml.js.map +1 -1
- package/dist/primitives/zip.d.ts +1 -0
- package/dist/primitives/zip.d.ts.map +1 -1
- package/dist/primitives/zip.js +21 -3
- package/dist/primitives/zip.js.map +1 -1
- package/dist/shared/field-structure.d.ts +14 -0
- package/dist/shared/field-structure.d.ts.map +1 -0
- package/dist/shared/field-structure.js +166 -0
- package/dist/shared/field-structure.js.map +1 -0
- package/dist/shared/ooxml/namespaces.d.ts +4 -1
- package/dist/shared/ooxml/namespaces.d.ts.map +1 -1
- package/dist/shared/ooxml/namespaces.js +4 -1
- package/dist/shared/ooxml/namespaces.js.map +1 -1
- package/package.json +13 -9
|
@@ -6,8 +6,11 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { OOXML, W } from './namespaces.js';
|
|
8
8
|
import { parseXml, serializeXml } from './xml.js';
|
|
9
|
-
import { getParagraphRuns, getParagraphText } from './text.js';
|
|
9
|
+
import { getParagraphRuns, getParagraphText, splitRunAtVisibleOffset } from './text.js';
|
|
10
10
|
import { getParagraphBookmarkId } from './bookmarks.js';
|
|
11
|
+
import { childElements, getLeafText, isW } from './dom-helpers.js';
|
|
12
|
+
import { getAttributeSafe } from './xml-helpers.js';
|
|
13
|
+
import { createRevisionContainer, prepareElementForDeletion, } from './track-changes-emitter.js';
|
|
11
14
|
// ── Relationship types ──────────────────────────────────────────────────
|
|
12
15
|
const REL_TYPE_COMMENTS = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments';
|
|
13
16
|
const REL_TYPE_COMMENTS_EXTENDED = 'http://schemas.microsoft.com/office/2011/relationships/commentsExtended';
|
|
@@ -16,6 +19,8 @@ const REL_TYPE_PEOPLE = 'http://schemas.microsoft.com/office/2011/relationships/
|
|
|
16
19
|
const CT_COMMENTS = 'application/vnd.openxmlformats-officedocument.wordprocessingml.comments+xml';
|
|
17
20
|
const CT_COMMENTS_EXTENDED = 'application/vnd.openxmlformats-officedocument.wordprocessingml.commentsExtended+xml';
|
|
18
21
|
const CT_PEOPLE = 'application/vnd.openxmlformats-officedocument.wordprocessingml.people+xml';
|
|
22
|
+
// XML Namespaces namespace — used when binding/declaring prefixes via setAttributeNS.
|
|
23
|
+
const XMLNS_NS = 'http://www.w3.org/2000/xmlns/';
|
|
19
24
|
// ── Minimal XML templates ───────────────────────────────────────────────
|
|
20
25
|
const COMMENTS_XML_TEMPLATE = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>` +
|
|
21
26
|
`<w:comments xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"` +
|
|
@@ -150,20 +155,24 @@ async function ensureRelationships(zip, newParts) {
|
|
|
150
155
|
* - Adds comment entry to comments.xml
|
|
151
156
|
* - Adds author to people.xml if not present
|
|
152
157
|
*/
|
|
153
|
-
export async function addComment(documentXml, zip, params) {
|
|
158
|
+
export async function addComment(documentXml, zip, params, ctx) {
|
|
154
159
|
const { paragraphEl, author, text, initials } = params;
|
|
160
|
+
const visibleLen = getParagraphText(paragraphEl).length;
|
|
155
161
|
const start = params.start ?? 0;
|
|
156
|
-
const end = params.end ??
|
|
162
|
+
const end = params.end ?? visibleLen;
|
|
157
163
|
if (start > end) {
|
|
158
164
|
throw new Error(`Invalid comment range: start (${start}) must be <= end (${end})`);
|
|
159
165
|
}
|
|
166
|
+
if (start < 0 || end > visibleLen) {
|
|
167
|
+
throw new Error(`Invalid comment range: [${start}, ${end}) is outside paragraph visible text [0, ${visibleLen})`);
|
|
168
|
+
}
|
|
160
169
|
// Load comments.xml
|
|
161
170
|
const commentsXml = await zip.readText('word/comments.xml');
|
|
162
171
|
const commentsDoc = parseXml(commentsXml);
|
|
163
172
|
// Allocate next comment ID
|
|
164
173
|
const commentId = allocateNextCommentId(commentsDoc);
|
|
165
174
|
// Insert range markers and reference in document body
|
|
166
|
-
insertCommentMarkers(documentXml, paragraphEl, commentId, start, end);
|
|
175
|
+
insertCommentMarkers(documentXml, paragraphEl, commentId, start, end, ctx);
|
|
167
176
|
// Add comment element to comments.xml
|
|
168
177
|
const paraId = generateParaId();
|
|
169
178
|
addCommentElement(commentsDoc, {
|
|
@@ -183,8 +192,11 @@ export async function addComment(documentXml, zip, params) {
|
|
|
183
192
|
*
|
|
184
193
|
* Replies don't have range markers in the document body.
|
|
185
194
|
* Thread linkage is stored in commentsExtended.xml via paraIdParent.
|
|
195
|
+
* `ctx` is accepted for API consistency with other comment mutations, but this
|
|
196
|
+
* primitive only updates comment side parts for replies, so no body revision
|
|
197
|
+
* markup is emitted here.
|
|
186
198
|
*/
|
|
187
|
-
export async function addCommentReply(_documentXml, zip, params) {
|
|
199
|
+
export async function addCommentReply(_documentXml, zip, params, _ctx) {
|
|
188
200
|
const { parentCommentId, author, text, initials } = params;
|
|
189
201
|
// Load comments.xml
|
|
190
202
|
const commentsXml = await zip.readText('word/comments.xml');
|
|
@@ -219,7 +231,7 @@ function allocateNextCommentId(commentsDoc) {
|
|
|
219
231
|
let maxId = -1;
|
|
220
232
|
for (let i = 0; i < commentEls.length; i++) {
|
|
221
233
|
const el = commentEls.item(i);
|
|
222
|
-
const idStr = el
|
|
234
|
+
const idStr = getAttributeSafe(el, OOXML.W_NS, 'id', 'w', { bareFallback: false });
|
|
223
235
|
if (idStr) {
|
|
224
236
|
const id = parseInt(idStr, 10);
|
|
225
237
|
if (id > maxId)
|
|
@@ -232,19 +244,42 @@ function findCommentParaId(commentsDoc, commentId) {
|
|
|
232
244
|
const commentEls = commentsDoc.getElementsByTagNameNS(OOXML.W_NS, W.comment);
|
|
233
245
|
for (let i = 0; i < commentEls.length; i++) {
|
|
234
246
|
const el = commentEls.item(i);
|
|
235
|
-
const idStr = el
|
|
247
|
+
const idStr = getAttributeSafe(el, OOXML.W_NS, 'id', 'w', { bareFallback: false });
|
|
236
248
|
if (idStr && parseInt(idStr, 10) === commentId) {
|
|
237
249
|
// paraId is on the w:p child inside the comment
|
|
238
250
|
const paras = el.getElementsByTagNameNS(OOXML.W_NS, W.p);
|
|
239
251
|
if (paras.length > 0) {
|
|
240
252
|
const p = paras.item(0);
|
|
241
|
-
return p
|
|
253
|
+
return getAttributeSafe(p, OOXML.W14_NS, 'paraId', 'w14', { bareFallback: false });
|
|
242
254
|
}
|
|
243
255
|
}
|
|
244
256
|
}
|
|
245
257
|
return null;
|
|
246
258
|
}
|
|
259
|
+
/**
|
|
260
|
+
* Ensure a comment-related document root binds the w14 and w15 prefixes before any
|
|
261
|
+
* w14:* / w15:* attribute is written into it. Real-world docx files often ship a
|
|
262
|
+
* pre-existing comments.xml (or commentsExtended.xml / people.xml) that omits one or
|
|
263
|
+
* both declarations; without a real namespace binding, xmldom would reject the
|
|
264
|
+
* round-tripped XML with `NamespaceError: prefix is non-null and namespace is null`.
|
|
265
|
+
*
|
|
266
|
+
* Uses `setAttributeNS(XMLNS_NS, …)` so the prefix is actually bound on the live DOM
|
|
267
|
+
* (not just serialized as a literal attribute) — that means subsequent `createElementNS`
|
|
268
|
+
* / `setAttributeNS` / `lookupNamespaceURI` calls on the same Document resolve correctly
|
|
269
|
+
* without depending on a serialize/reparse round trip. Idempotent — guards on the real
|
|
270
|
+
* binding via `lookupNamespaceURI`, not on a same-named plain attribute.
|
|
271
|
+
*/
|
|
272
|
+
function ensureCommentPartNamespaceAliases(commentsDoc) {
|
|
273
|
+
const root = commentsDoc.documentElement;
|
|
274
|
+
if (root.lookupNamespaceURI('w14') !== OOXML.W14_NS) {
|
|
275
|
+
root.setAttributeNS(XMLNS_NS, 'xmlns:w14', OOXML.W14_NS);
|
|
276
|
+
}
|
|
277
|
+
if (root.lookupNamespaceURI('w15') !== OOXML.W15_NS) {
|
|
278
|
+
root.setAttributeNS(XMLNS_NS, 'xmlns:w15', OOXML.W15_NS);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
247
281
|
function addCommentElement(commentsDoc, params) {
|
|
282
|
+
ensureCommentPartNamespaceAliases(commentsDoc);
|
|
248
283
|
const root = commentsDoc.documentElement;
|
|
249
284
|
const commentEl = commentsDoc.createElementNS(OOXML.W_NS, 'w:comment');
|
|
250
285
|
commentEl.setAttribute('w:id', String(params.id));
|
|
@@ -253,7 +288,9 @@ function addCommentElement(commentsDoc, params) {
|
|
|
253
288
|
commentEl.setAttribute('w:initials', params.initials);
|
|
254
289
|
// Comment body: <w:p w14:paraId="..."><w:pPr><w:pStyle w:val="CommentText"/></w:pPr><w:r><w:annotationRef/></w:r><w:r><w:t>text</w:t></w:r></w:p>
|
|
255
290
|
const p = commentsDoc.createElementNS(OOXML.W_NS, 'w:p');
|
|
256
|
-
|
|
291
|
+
// Use setAttributeNS so the attribute carries a real namespace URI — otherwise xmldom
|
|
292
|
+
// serializes a prefix it cannot resolve and reparse throws NamespaceError (#154).
|
|
293
|
+
p.setAttributeNS(OOXML.W14_NS, 'w14:paraId', params.paraId);
|
|
257
294
|
// Annotation reference run
|
|
258
295
|
const refRun = commentsDoc.createElementNS(OOXML.W_NS, 'w:r');
|
|
259
296
|
const annotRef = commentsDoc.createElementNS(OOXML.W_NS, 'w:annotationRef');
|
|
@@ -271,10 +308,8 @@ function addCommentElement(commentsDoc, params) {
|
|
|
271
308
|
commentEl.appendChild(p);
|
|
272
309
|
root.appendChild(commentEl);
|
|
273
310
|
}
|
|
274
|
-
function insertCommentMarkers(documentXml, paragraphEl, commentId, start, end) {
|
|
275
|
-
// Find the runs in the paragraph and map string offsets to DOM positions
|
|
311
|
+
function insertCommentMarkers(documentXml, paragraphEl, commentId, start, end, ctx) {
|
|
276
312
|
const runs = getParagraphRuns(paragraphEl);
|
|
277
|
-
// Create marker elements
|
|
278
313
|
const rangeStart = documentXml.createElementNS(OOXML.W_NS, 'w:commentRangeStart');
|
|
279
314
|
rangeStart.setAttribute('w:id', String(commentId));
|
|
280
315
|
const rangeEnd = documentXml.createElementNS(OOXML.W_NS, 'w:commentRangeEnd');
|
|
@@ -288,93 +323,180 @@ function insertCommentMarkers(documentXml, paragraphEl, commentId, start, end) {
|
|
|
288
323
|
const commentRef = documentXml.createElementNS(OOXML.W_NS, 'w:commentReference');
|
|
289
324
|
commentRef.setAttribute('w:id', String(commentId));
|
|
290
325
|
refRun.appendChild(commentRef);
|
|
291
|
-
|
|
326
|
+
const refAnchor = ctx ? createRevisionContainer(documentXml, 'ins', ctx) : refRun;
|
|
327
|
+
if (ctx) {
|
|
328
|
+
refAnchor.appendChild(refRun);
|
|
329
|
+
}
|
|
330
|
+
if (runs.length === 0) {
|
|
331
|
+
paragraphEl.appendChild(rangeStart);
|
|
332
|
+
paragraphEl.appendChild(rangeEnd);
|
|
333
|
+
paragraphEl.appendChild(refAnchor);
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
const mapping = mapOffsetsToRuns(runs, start, end);
|
|
337
|
+
const { startRunIdx, startOffset, endRunIdx, endOffset } = mapping;
|
|
338
|
+
// Collapsed range (start === end): insert both markers at the same boundary.
|
|
339
|
+
// Splitting again here would create an empty <w:r> inside the marker pair —
|
|
340
|
+
// replaceParagraphTextRange() avoids this by deleting the temporary run, which
|
|
341
|
+
// we can't do for comments. Handle the boundary directly.
|
|
342
|
+
if (startRunIdx === endRunIdx && startOffset === endOffset) {
|
|
343
|
+
insertCollapsedRangeMarkers(runs[startRunIdx], startOffset, rangeStart, rangeEnd, refAnchor);
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
// Split boundary runs at the exact visible-text offsets so the markers can sit
|
|
347
|
+
// on true sub-paragraph boundaries instead of being snapped to whole-run edges.
|
|
348
|
+
// Choreography mirrors replaceParagraphTextRange() in text.ts:404.
|
|
349
|
+
let startRunEl = runs[startRunIdx].r;
|
|
350
|
+
let endRunEl = runs[endRunIdx].r;
|
|
351
|
+
if (startRunIdx === endRunIdx) {
|
|
352
|
+
const runLen = runs[startRunIdx].text.length;
|
|
353
|
+
if (endOffset < runLen) {
|
|
354
|
+
const { left } = splitRunAtVisibleOffset(startRunEl, endOffset);
|
|
355
|
+
startRunEl = left;
|
|
356
|
+
endRunEl = left;
|
|
357
|
+
}
|
|
358
|
+
if (startOffset > 0) {
|
|
359
|
+
const { right } = splitRunAtVisibleOffset(startRunEl, startOffset);
|
|
360
|
+
startRunEl = right;
|
|
361
|
+
endRunEl = right;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
else {
|
|
365
|
+
if (startOffset > 0) {
|
|
366
|
+
const { right } = splitRunAtVisibleOffset(startRunEl, startOffset);
|
|
367
|
+
startRunEl = right;
|
|
368
|
+
}
|
|
369
|
+
const endLen = runs[endRunIdx].text.length;
|
|
370
|
+
if (endOffset < endLen) {
|
|
371
|
+
const { left } = splitRunAtVisibleOffset(endRunEl, endOffset);
|
|
372
|
+
endRunEl = left;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
// Insert relative to each run's parent so anchors inside w:hyperlink, w:ins,
|
|
376
|
+
// w:del, w:sdtContent, etc. keep the markers inside the wrapper.
|
|
377
|
+
const startParent = startRunEl.parentNode;
|
|
378
|
+
const endParent = endRunEl.parentNode;
|
|
379
|
+
if (!startParent || !endParent) {
|
|
380
|
+
throw new Error('Split run has no parent');
|
|
381
|
+
}
|
|
382
|
+
startParent.insertBefore(rangeStart, startRunEl);
|
|
383
|
+
endParent.insertBefore(rangeEnd, endRunEl.nextSibling);
|
|
384
|
+
endParent.insertBefore(refAnchor, rangeEnd.nextSibling);
|
|
385
|
+
}
|
|
386
|
+
function mapOffsetsToRuns(runs, start, end) {
|
|
387
|
+
// Map visible-text offsets to (runIndex, offsetInRun). Caller validates that
|
|
388
|
+
// 0 <= start <= end <= sum(runs[i].text.length).
|
|
292
389
|
let pos = 0;
|
|
293
390
|
let startRunIdx = -1;
|
|
294
391
|
let endRunIdx = -1;
|
|
392
|
+
let startOffset = 0;
|
|
393
|
+
let endOffset = 0;
|
|
295
394
|
for (let i = 0; i < runs.length; i++) {
|
|
296
|
-
const
|
|
297
|
-
if (startRunIdx
|
|
395
|
+
const len = runs[i].text.length;
|
|
396
|
+
if (startRunIdx === -1 && start >= pos && start <= pos + len) {
|
|
298
397
|
startRunIdx = i;
|
|
299
|
-
|
|
398
|
+
startOffset = start - pos;
|
|
399
|
+
}
|
|
400
|
+
if (endRunIdx === -1 && end >= pos && end <= pos + len) {
|
|
300
401
|
endRunIdx = i;
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
startRunIdx = 0;
|
|
306
|
-
if (endRunIdx < 0)
|
|
307
|
-
endRunIdx = runs.length - 1;
|
|
308
|
-
// Insert commentRangeStart before the start run
|
|
309
|
-
if (runs.length > 0 && startRunIdx < runs.length) {
|
|
310
|
-
paragraphEl.insertBefore(rangeStart, runs[startRunIdx].r);
|
|
311
|
-
}
|
|
312
|
-
else {
|
|
313
|
-
// No runs — insert at end of paragraph
|
|
314
|
-
paragraphEl.appendChild(rangeStart);
|
|
402
|
+
endOffset = end - pos;
|
|
403
|
+
break;
|
|
404
|
+
}
|
|
405
|
+
pos += len;
|
|
315
406
|
}
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
const afterEndRun = runs[endRunIdx].r.nextSibling;
|
|
319
|
-
paragraphEl.insertBefore(rangeEnd, afterEndRun);
|
|
320
|
-
paragraphEl.insertBefore(refRun, afterEndRun);
|
|
321
|
-
}
|
|
322
|
-
else {
|
|
323
|
-
paragraphEl.appendChild(rangeEnd);
|
|
324
|
-
paragraphEl.appendChild(refRun);
|
|
407
|
+
if (startRunIdx === -1 || endRunIdx === -1) {
|
|
408
|
+
throw new Error(`Could not map offsets [${start}, ${end}) to runs`);
|
|
325
409
|
}
|
|
410
|
+
return { startRunIdx, startOffset, endRunIdx, endOffset };
|
|
411
|
+
}
|
|
412
|
+
function insertCollapsedRangeMarkers(run, offsetInRun, rangeStart, rangeEnd, refAnchor) {
|
|
413
|
+
const runEl = run.r;
|
|
414
|
+
const parent = runEl.parentNode;
|
|
415
|
+
if (!parent)
|
|
416
|
+
throw new Error('Run has no parent');
|
|
417
|
+
const runLen = run.text.length;
|
|
418
|
+
if (offsetInRun === 0) {
|
|
419
|
+
// Insert before the run.
|
|
420
|
+
parent.insertBefore(rangeStart, runEl);
|
|
421
|
+
parent.insertBefore(rangeEnd, runEl);
|
|
422
|
+
parent.insertBefore(refAnchor, runEl);
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
if (offsetInRun === runLen) {
|
|
426
|
+
// Insert after the run. Capture nextSibling once because insertBefore
|
|
427
|
+
// shifts it (rangeStart would otherwise land last instead of first).
|
|
428
|
+
const ref = runEl.nextSibling;
|
|
429
|
+
parent.insertBefore(rangeStart, ref);
|
|
430
|
+
parent.insertBefore(rangeEnd, ref);
|
|
431
|
+
parent.insertBefore(refAnchor, ref);
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
// Mid-run: split once so the markers sit between the two halves.
|
|
435
|
+
const { right } = splitRunAtVisibleOffset(runEl, offsetInRun);
|
|
436
|
+
parent.insertBefore(rangeStart, right);
|
|
437
|
+
parent.insertBefore(rangeEnd, right);
|
|
438
|
+
parent.insertBefore(refAnchor, right);
|
|
326
439
|
}
|
|
327
440
|
async function linkReplyInCommentsExtended(zip, replyParaId, parentParaId) {
|
|
328
441
|
const extXml = await zip.readText('word/commentsExtended.xml');
|
|
329
442
|
const extDoc = parseXml(extXml);
|
|
443
|
+
ensureCommentPartNamespaceAliases(extDoc);
|
|
330
444
|
const root = extDoc.documentElement;
|
|
331
445
|
const exEl = extDoc.createElementNS(OOXML.W15_NS, 'w15:commentEx');
|
|
332
|
-
exEl.
|
|
333
|
-
exEl.
|
|
334
|
-
exEl.
|
|
446
|
+
exEl.setAttributeNS(OOXML.W15_NS, 'w15:paraId', replyParaId);
|
|
447
|
+
exEl.setAttributeNS(OOXML.W15_NS, 'w15:paraIdParent', parentParaId);
|
|
448
|
+
exEl.setAttributeNS(OOXML.W15_NS, 'w15:done', '0');
|
|
335
449
|
root.appendChild(exEl);
|
|
336
450
|
zip.writeText('word/commentsExtended.xml', serializeXml(extDoc));
|
|
337
451
|
}
|
|
338
452
|
async function ensureCommentExEntry(zip, paraId) {
|
|
339
453
|
const extXml = await zip.readText('word/commentsExtended.xml');
|
|
340
454
|
const extDoc = parseXml(extXml);
|
|
455
|
+
ensureCommentPartNamespaceAliases(extDoc);
|
|
341
456
|
const root = extDoc.documentElement;
|
|
342
457
|
// Check if entry already exists
|
|
343
458
|
const existing = root.getElementsByTagNameNS(OOXML.W15_NS, 'commentEx');
|
|
344
459
|
for (let i = 0; i < existing.length; i++) {
|
|
345
460
|
const el = existing.item(i);
|
|
346
|
-
const pid = el
|
|
461
|
+
const pid = getAttributeSafe(el, OOXML.W15_NS, 'paraId', 'w15', { bareFallback: false });
|
|
347
462
|
if (pid === paraId)
|
|
348
463
|
return; // Already present
|
|
349
464
|
}
|
|
350
465
|
const exEl = extDoc.createElementNS(OOXML.W15_NS, 'w15:commentEx');
|
|
351
|
-
exEl.
|
|
352
|
-
exEl.
|
|
466
|
+
exEl.setAttributeNS(OOXML.W15_NS, 'w15:paraId', paraId);
|
|
467
|
+
exEl.setAttributeNS(OOXML.W15_NS, 'w15:done', '0');
|
|
353
468
|
root.appendChild(exEl);
|
|
354
469
|
zip.writeText('word/commentsExtended.xml', serializeXml(extDoc));
|
|
355
470
|
}
|
|
356
471
|
async function ensureAuthorInPeople(zip, author) {
|
|
357
472
|
const peopleXml = await zip.readText('word/people.xml');
|
|
358
473
|
const peopleDoc = parseXml(peopleXml);
|
|
474
|
+
ensureCommentPartNamespaceAliases(peopleDoc);
|
|
359
475
|
const root = peopleDoc.documentElement;
|
|
360
476
|
// Check if author already exists
|
|
361
477
|
const persons = root.getElementsByTagNameNS(OOXML.W15_NS, 'person');
|
|
362
478
|
for (let i = 0; i < persons.length; i++) {
|
|
363
479
|
const el = persons.item(i);
|
|
364
|
-
const name = el
|
|
480
|
+
const name = getAttributeSafe(el, OOXML.W15_NS, 'author', 'w15', { bareFallback: false });
|
|
365
481
|
if (name === author)
|
|
366
482
|
return; // Already present
|
|
367
483
|
}
|
|
368
484
|
const personEl = peopleDoc.createElementNS(OOXML.W15_NS, 'w15:person');
|
|
369
|
-
personEl.
|
|
485
|
+
personEl.setAttributeNS(OOXML.W15_NS, 'w15:author', author);
|
|
370
486
|
// Add a presenceInfo child (required by Word)
|
|
371
487
|
const presenceInfo = peopleDoc.createElementNS(OOXML.W15_NS, 'w15:presenceInfo');
|
|
372
|
-
presenceInfo.
|
|
373
|
-
presenceInfo.
|
|
488
|
+
presenceInfo.setAttributeNS(OOXML.W15_NS, 'w15:providerId', 'None');
|
|
489
|
+
presenceInfo.setAttributeNS(OOXML.W15_NS, 'w15:userId', author);
|
|
374
490
|
personEl.appendChild(presenceInfo);
|
|
375
491
|
root.appendChild(personEl);
|
|
376
492
|
zip.writeText('word/people.xml', serializeXml(peopleDoc));
|
|
377
493
|
}
|
|
494
|
+
var FieldState;
|
|
495
|
+
(function (FieldState) {
|
|
496
|
+
FieldState[FieldState["OUTSIDE_FIELD"] = 0] = "OUTSIDE_FIELD";
|
|
497
|
+
FieldState[FieldState["IN_FIELD_CODE"] = 1] = "IN_FIELD_CODE";
|
|
498
|
+
FieldState[FieldState["IN_FIELD_RESULT"] = 2] = "IN_FIELD_RESULT";
|
|
499
|
+
})(FieldState || (FieldState = {}));
|
|
378
500
|
/**
|
|
379
501
|
* Read all comments from a document, building a threaded tree.
|
|
380
502
|
*
|
|
@@ -390,27 +512,30 @@ export async function getComments(zip, documentXml) {
|
|
|
390
512
|
const commentEls = commentsDoc.getElementsByTagNameNS(OOXML.W_NS, W.comment);
|
|
391
513
|
if (commentEls.length === 0)
|
|
392
514
|
return [];
|
|
515
|
+
const rangeMetadata = resolveCommentRangeMetadata(documentXml);
|
|
393
516
|
// Build a map of commentId → { paraId, Comment }
|
|
394
517
|
const byParaId = new Map();
|
|
395
518
|
const byId = new Map();
|
|
396
519
|
for (let i = 0; i < commentEls.length; i++) {
|
|
397
520
|
const el = commentEls.item(i);
|
|
398
|
-
const idStr = el
|
|
521
|
+
const idStr = getAttributeSafe(el, OOXML.W_NS, 'id', 'w', { bareFallback: false });
|
|
399
522
|
const id = idStr ? parseInt(idStr, 10) : -1;
|
|
400
523
|
if (id < 0)
|
|
401
524
|
continue;
|
|
402
|
-
const author = el
|
|
403
|
-
const date = el
|
|
404
|
-
const initials = el
|
|
525
|
+
const author = getAttributeSafe(el, OOXML.W_NS, 'author', 'w', { bareFallback: false }) ?? '';
|
|
526
|
+
const date = getAttributeSafe(el, OOXML.W_NS, 'date', 'w', { bareFallback: false }) ?? '';
|
|
527
|
+
const initials = getAttributeSafe(el, OOXML.W_NS, 'initials', 'w', { bareFallback: false }) ?? '';
|
|
405
528
|
// Extract text from <w:t> elements, skipping annotationRef runs
|
|
406
529
|
const text = extractCommentText(el);
|
|
407
|
-
// Get paraId from first <w:p> child
|
|
530
|
+
// Get paraId from first <w:p> child (namespace-aware to handle non-`w` prefixes)
|
|
408
531
|
const paras = el.getElementsByTagNameNS(OOXML.W_NS, W.p);
|
|
409
532
|
let paragraphId = null;
|
|
410
533
|
if (paras.length > 0) {
|
|
411
534
|
const p = paras.item(0);
|
|
412
|
-
paragraphId = p
|
|
535
|
+
paragraphId = getAttributeSafe(p, OOXML.W14_NS, 'paraId', 'w14', { bareFallback: false });
|
|
413
536
|
}
|
|
537
|
+
const startPoint = rangeMetadata.startById.get(id);
|
|
538
|
+
const endPoint = rangeMetadata.endById.get(id);
|
|
414
539
|
const comment = {
|
|
415
540
|
id,
|
|
416
541
|
author,
|
|
@@ -418,35 +543,18 @@ export async function getComments(zip, documentXml) {
|
|
|
418
543
|
initials,
|
|
419
544
|
text,
|
|
420
545
|
paragraphId,
|
|
421
|
-
anchoredParagraphId: null,
|
|
546
|
+
anchoredParagraphId: startPoint?.paragraphId ?? null,
|
|
547
|
+
endParagraphId: endPoint?.paragraphId ?? startPoint?.paragraphId ?? null,
|
|
548
|
+
startRunIndex: startPoint?.runIndex,
|
|
549
|
+
startCharOffset: startPoint?.charOffset,
|
|
550
|
+
endRunIndex: endPoint?.runIndex,
|
|
551
|
+
endCharOffset: endPoint?.charOffset,
|
|
422
552
|
replies: [],
|
|
423
553
|
};
|
|
424
554
|
byId.set(id, comment);
|
|
425
555
|
if (paragraphId)
|
|
426
556
|
byParaId.set(paragraphId, comment);
|
|
427
557
|
}
|
|
428
|
-
// Resolve anchoredParagraphId by scanning documentXml for commentRangeStart elements
|
|
429
|
-
const rangeStarts = documentXml.getElementsByTagNameNS(OOXML.W_NS, W.commentRangeStart);
|
|
430
|
-
for (let i = 0; i < rangeStarts.length; i++) {
|
|
431
|
-
const rs = rangeStarts.item(i);
|
|
432
|
-
const cidStr = rs.getAttributeNS(OOXML.W_NS, 'id') ?? rs.getAttribute('w:id');
|
|
433
|
-
if (!cidStr)
|
|
434
|
-
continue;
|
|
435
|
-
const cid = parseInt(cidStr, 10);
|
|
436
|
-
const comment = byId.get(cid);
|
|
437
|
-
if (!comment)
|
|
438
|
-
continue;
|
|
439
|
-
// Walk up to find enclosing <w:p>
|
|
440
|
-
let parent = rs.parentNode;
|
|
441
|
-
while (parent && parent.nodeType === 1) {
|
|
442
|
-
const pel = parent;
|
|
443
|
-
if (pel.localName === W.p && pel.namespaceURI === OOXML.W_NS) {
|
|
444
|
-
comment.anchoredParagraphId = getParagraphBookmarkId(pel);
|
|
445
|
-
break;
|
|
446
|
-
}
|
|
447
|
-
parent = parent.parentNode;
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
558
|
// Build thread tree from commentsExtended.xml
|
|
451
559
|
const extText = await zip.readTextOrNull('word/commentsExtended.xml');
|
|
452
560
|
if (extText) {
|
|
@@ -454,8 +562,8 @@ export async function getComments(zip, documentXml) {
|
|
|
454
562
|
const exEls = extDoc.getElementsByTagNameNS(OOXML.W15_NS, 'commentEx');
|
|
455
563
|
for (let i = 0; i < exEls.length; i++) {
|
|
456
564
|
const ex = exEls.item(i);
|
|
457
|
-
const childParaId = ex
|
|
458
|
-
const parentParaId = ex
|
|
565
|
+
const childParaId = getAttributeSafe(ex, OOXML.W15_NS, 'paraId', 'w15', { bareFallback: false });
|
|
566
|
+
const parentParaId = getAttributeSafe(ex, OOXML.W15_NS, 'paraIdParent', 'w15', { bareFallback: false });
|
|
459
567
|
if (!childParaId || !parentParaId)
|
|
460
568
|
continue;
|
|
461
569
|
const child = byParaId.get(childParaId);
|
|
@@ -472,8 +580,8 @@ export async function getComments(zip, documentXml) {
|
|
|
472
580
|
const exEls = extDoc.getElementsByTagNameNS(OOXML.W15_NS, 'commentEx');
|
|
473
581
|
for (let i = 0; i < exEls.length; i++) {
|
|
474
582
|
const ex = exEls.item(i);
|
|
475
|
-
const childParaId = ex
|
|
476
|
-
const parentParaId = ex
|
|
583
|
+
const childParaId = getAttributeSafe(ex, OOXML.W15_NS, 'paraId', 'w15', { bareFallback: false });
|
|
584
|
+
const parentParaId = getAttributeSafe(ex, OOXML.W15_NS, 'paraIdParent', 'w15', { bareFallback: false });
|
|
477
585
|
if (childParaId && parentParaId) {
|
|
478
586
|
replyParaIds.add(childParaId);
|
|
479
587
|
}
|
|
@@ -487,6 +595,155 @@ export async function getComments(zip, documentXml) {
|
|
|
487
595
|
}
|
|
488
596
|
return roots;
|
|
489
597
|
}
|
|
598
|
+
function resolveCommentRangeMetadata(documentXml) {
|
|
599
|
+
const startById = new Map();
|
|
600
|
+
const endById = new Map();
|
|
601
|
+
const root = documentXml.documentElement;
|
|
602
|
+
if (!root)
|
|
603
|
+
return { startById, endById };
|
|
604
|
+
const paragraphList = root.getElementsByTagNameNS(OOXML.W_NS, W.p);
|
|
605
|
+
for (let i = 0; i < paragraphList.length; i++) {
|
|
606
|
+
resolveCommentRangeMetadataInParagraph(paragraphList.item(i), startById, endById);
|
|
607
|
+
}
|
|
608
|
+
return { startById, endById };
|
|
609
|
+
}
|
|
610
|
+
function resolveCommentRangeMetadataInParagraph(paragraph, startById, endById) {
|
|
611
|
+
const walkState = {
|
|
612
|
+
charPos: 0,
|
|
613
|
+
fieldState: FieldState.OUTSIDE_FIELD,
|
|
614
|
+
allRuns: [],
|
|
615
|
+
currentRunIndex: 0,
|
|
616
|
+
startMarkers: [],
|
|
617
|
+
endMarkers: [],
|
|
618
|
+
};
|
|
619
|
+
walkParagraphForCommentMarkers(paragraph, paragraph, walkState);
|
|
620
|
+
const paragraphId = getParagraphBookmarkId(paragraph);
|
|
621
|
+
for (const marker of walkState.startMarkers) {
|
|
622
|
+
if (startById.has(marker.id))
|
|
623
|
+
continue;
|
|
624
|
+
startById.set(marker.id, {
|
|
625
|
+
paragraphId,
|
|
626
|
+
...resolveMarkerToRunBoundary(walkState.allRuns, marker, 'start'),
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
for (const marker of walkState.endMarkers) {
|
|
630
|
+
if (endById.has(marker.id))
|
|
631
|
+
continue;
|
|
632
|
+
endById.set(marker.id, {
|
|
633
|
+
paragraphId,
|
|
634
|
+
...resolveMarkerToRunBoundary(walkState.allRuns, marker, 'end'),
|
|
635
|
+
});
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
function walkParagraphForCommentMarkers(rootParagraph, node, state) {
|
|
639
|
+
for (const child of childElements(node)) {
|
|
640
|
+
if (child !== rootParagraph && isW(child, W.p))
|
|
641
|
+
continue;
|
|
642
|
+
if (isW(child, W.commentRangeStart)) {
|
|
643
|
+
recordParagraphLevelMarker(child, state.startMarkers, state);
|
|
644
|
+
continue;
|
|
645
|
+
}
|
|
646
|
+
if (isW(child, W.commentRangeEnd)) {
|
|
647
|
+
recordParagraphLevelMarker(child, state.endMarkers, state);
|
|
648
|
+
continue;
|
|
649
|
+
}
|
|
650
|
+
if (isW(child, W.r)) {
|
|
651
|
+
walkRunForCommentMarkers(child, state);
|
|
652
|
+
continue;
|
|
653
|
+
}
|
|
654
|
+
walkParagraphForCommentMarkers(rootParagraph, child, state);
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
function walkRunForCommentMarkers(run, state) {
|
|
658
|
+
const runIndex = state.currentRunIndex;
|
|
659
|
+
state.currentRunIndex += 1;
|
|
660
|
+
const runVisibleStart = state.charPos;
|
|
661
|
+
for (const child of childElements(run)) {
|
|
662
|
+
if (isW(child, W.commentRangeStart)) {
|
|
663
|
+
recordInRunMarker(child, state.startMarkers, runIndex, state.charPos - runVisibleStart);
|
|
664
|
+
continue;
|
|
665
|
+
}
|
|
666
|
+
if (isW(child, W.commentRangeEnd)) {
|
|
667
|
+
recordInRunMarker(child, state.endMarkers, runIndex, state.charPos - runVisibleStart);
|
|
668
|
+
continue;
|
|
669
|
+
}
|
|
670
|
+
if (!child.namespaceURI || child.namespaceURI !== OOXML.W_NS)
|
|
671
|
+
continue;
|
|
672
|
+
if (child.localName === W.fldChar) {
|
|
673
|
+
const type = getWordAttribute(child, 'fldCharType') ?? '';
|
|
674
|
+
if (type === 'begin')
|
|
675
|
+
state.fieldState = FieldState.IN_FIELD_CODE;
|
|
676
|
+
else if (type === 'separate')
|
|
677
|
+
state.fieldState = FieldState.IN_FIELD_RESULT;
|
|
678
|
+
else if (type === 'end')
|
|
679
|
+
state.fieldState = FieldState.OUTSIDE_FIELD;
|
|
680
|
+
continue;
|
|
681
|
+
}
|
|
682
|
+
if (state.fieldState === FieldState.IN_FIELD_CODE)
|
|
683
|
+
continue;
|
|
684
|
+
if (child.localName === W.t) {
|
|
685
|
+
state.charPos += (getLeafText(child) ?? '').length;
|
|
686
|
+
continue;
|
|
687
|
+
}
|
|
688
|
+
if (child.localName === W.tab || child.localName === W.br) {
|
|
689
|
+
state.charPos += 1;
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
state.allRuns.push({ visibleLength: state.charPos - runVisibleStart });
|
|
693
|
+
}
|
|
694
|
+
function recordInRunMarker(markerEl, bucket, runIndex, charOffset) {
|
|
695
|
+
const id = getCommentMarkerId(markerEl);
|
|
696
|
+
if (id == null)
|
|
697
|
+
return;
|
|
698
|
+
bucket.push({ id, inside: { runIndex, charOffset } });
|
|
699
|
+
}
|
|
700
|
+
function recordParagraphLevelMarker(markerEl, bucket, state) {
|
|
701
|
+
const id = getCommentMarkerId(markerEl);
|
|
702
|
+
if (id == null)
|
|
703
|
+
return;
|
|
704
|
+
bucket.push({ id, between: { afterRunIndex: state.currentRunIndex - 1 } });
|
|
705
|
+
}
|
|
706
|
+
function getCommentMarkerId(markerEl) {
|
|
707
|
+
const idStr = getAttributeSafe(markerEl, OOXML.W_NS, 'id', 'w', { bareFallback: false });
|
|
708
|
+
if (!idStr)
|
|
709
|
+
return null;
|
|
710
|
+
const id = parseInt(idStr, 10);
|
|
711
|
+
return Number.isNaN(id) ? null : id;
|
|
712
|
+
}
|
|
713
|
+
function getWordAttribute(el, localName) {
|
|
714
|
+
return getAttributeSafe(el, OOXML.W_NS, localName, 'w');
|
|
715
|
+
}
|
|
716
|
+
function resolveMarkerToRunBoundary(allRuns, marker, boundary) {
|
|
717
|
+
// Marker seen INSIDE a run already carries the exact runIndex + charOffset.
|
|
718
|
+
if (marker.inside) {
|
|
719
|
+
return { runIndex: marker.inside.runIndex, charOffset: marker.inside.charOffset };
|
|
720
|
+
}
|
|
721
|
+
if (!marker.between)
|
|
722
|
+
return {};
|
|
723
|
+
if (allRuns.length === 0)
|
|
724
|
+
return {};
|
|
725
|
+
const { afterRunIndex } = marker.between;
|
|
726
|
+
const lastIndex = allRuns.length - 1;
|
|
727
|
+
if (boundary === 'start') {
|
|
728
|
+
// Marker sits between run `afterRunIndex` and run `afterRunIndex + 1`.
|
|
729
|
+
// For a `start` marker, the range opens at offset 0 of the next run if it exists.
|
|
730
|
+
const nextIdx = afterRunIndex + 1;
|
|
731
|
+
if (nextIdx <= lastIndex) {
|
|
732
|
+
return { runIndex: nextIdx, charOffset: 0 };
|
|
733
|
+
}
|
|
734
|
+
// Marker is past the last run — clamp to end of last run.
|
|
735
|
+
return { runIndex: lastIndex, charOffset: allRuns[lastIndex].visibleLength };
|
|
736
|
+
}
|
|
737
|
+
// boundary === 'end': range closes at the end of the previous run if one exists.
|
|
738
|
+
if (afterRunIndex < 0) {
|
|
739
|
+
// Marker is before the first run — empty range starting at run 0.
|
|
740
|
+
return { runIndex: 0, charOffset: 0 };
|
|
741
|
+
}
|
|
742
|
+
if (afterRunIndex <= lastIndex) {
|
|
743
|
+
return { runIndex: afterRunIndex, charOffset: allRuns[afterRunIndex].visibleLength };
|
|
744
|
+
}
|
|
745
|
+
return { runIndex: lastIndex, charOffset: allRuns[lastIndex].visibleLength };
|
|
746
|
+
}
|
|
490
747
|
/**
|
|
491
748
|
* Get a single comment by ID, searching the full tree including replies.
|
|
492
749
|
*/
|
|
@@ -514,7 +771,7 @@ function findCommentById(comments, id) {
|
|
|
514
771
|
* commentReference from document.xml (element-level; run removed only if empty)
|
|
515
772
|
* - Transitive cascade: deleting any node also deletes all descendants
|
|
516
773
|
*/
|
|
517
|
-
export async function deleteComment(documentXml, zip, params) {
|
|
774
|
+
export async function deleteComment(documentXml, zip, params, ctx) {
|
|
518
775
|
const { commentId } = params;
|
|
519
776
|
const commentsText = await zip.readTextOrNull('word/comments.xml');
|
|
520
777
|
if (!commentsText)
|
|
@@ -535,7 +792,7 @@ export async function deleteComment(documentXml, zip, params) {
|
|
|
535
792
|
const allCommentEls = commentsDoc.getElementsByTagNameNS(OOXML.W_NS, W.comment);
|
|
536
793
|
for (let i = 0; i < allCommentEls.length; i++) {
|
|
537
794
|
const el = allCommentEls.item(i);
|
|
538
|
-
const idStr = el
|
|
795
|
+
const idStr = getAttributeSafe(el, OOXML.W_NS, 'id', 'w', { bareFallback: false });
|
|
539
796
|
const id = idStr ? parseInt(idStr, 10) : -1;
|
|
540
797
|
if (id < 0)
|
|
541
798
|
continue;
|
|
@@ -552,8 +809,8 @@ export async function deleteComment(documentXml, zip, params) {
|
|
|
552
809
|
const childrenOf = new Map();
|
|
553
810
|
for (let i = 0; i < exEls.length; i++) {
|
|
554
811
|
const ex = exEls.item(i);
|
|
555
|
-
const childPid = ex
|
|
556
|
-
const parentPid = ex
|
|
812
|
+
const childPid = getAttributeSafe(ex, OOXML.W15_NS, 'paraId', 'w15', { bareFallback: false });
|
|
813
|
+
const parentPid = getAttributeSafe(ex, OOXML.W15_NS, 'paraIdParent', 'w15', { bareFallback: false });
|
|
557
814
|
if (childPid && parentPid) {
|
|
558
815
|
const arr = childrenOf.get(parentPid);
|
|
559
816
|
if (arr)
|
|
@@ -584,7 +841,7 @@ export async function deleteComment(documentXml, zip, params) {
|
|
|
584
841
|
const elsToRemove = [];
|
|
585
842
|
for (let i = 0; i < allCommentEls.length; i++) {
|
|
586
843
|
const el = allCommentEls.item(i);
|
|
587
|
-
const idStr = el
|
|
844
|
+
const idStr = getAttributeSafe(el, OOXML.W_NS, 'id', 'w', { bareFallback: false });
|
|
588
845
|
const id = idStr ? parseInt(idStr, 10) : -1;
|
|
589
846
|
if (idsToDelete.has(id))
|
|
590
847
|
elsToRemove.push(el);
|
|
@@ -600,7 +857,7 @@ export async function deleteComment(documentXml, zip, params) {
|
|
|
600
857
|
const exToRemove = [];
|
|
601
858
|
for (let i = 0; i < exEls.length; i++) {
|
|
602
859
|
const ex = exEls.item(i);
|
|
603
|
-
const pid = ex
|
|
860
|
+
const pid = getAttributeSafe(ex, OOXML.W15_NS, 'paraId', 'w15', { bareFallback: false });
|
|
604
861
|
if (pid && paraIdsToDelete.has(pid))
|
|
605
862
|
exToRemove.push(ex);
|
|
606
863
|
}
|
|
@@ -611,14 +868,14 @@ export async function deleteComment(documentXml, zip, params) {
|
|
|
611
868
|
}
|
|
612
869
|
// 3. Remove range markers and commentReference from document.xml (for root comments)
|
|
613
870
|
for (const cid of idsToDelete) {
|
|
614
|
-
removeCommentMarkersFromDocument(documentXml, cid);
|
|
871
|
+
removeCommentMarkersFromDocument(documentXml, cid, ctx);
|
|
615
872
|
}
|
|
616
873
|
}
|
|
617
874
|
function findCommentElementById(commentsDoc, commentId) {
|
|
618
875
|
const commentEls = commentsDoc.getElementsByTagNameNS(OOXML.W_NS, W.comment);
|
|
619
876
|
for (let i = 0; i < commentEls.length; i++) {
|
|
620
877
|
const el = commentEls.item(i);
|
|
621
|
-
const idStr = el
|
|
878
|
+
const idStr = getAttributeSafe(el, OOXML.W_NS, 'id', 'w', { bareFallback: false });
|
|
622
879
|
if (idStr && parseInt(idStr, 10) === commentId)
|
|
623
880
|
return el;
|
|
624
881
|
}
|
|
@@ -629,16 +886,16 @@ function getCommentElParaId(commentEl) {
|
|
|
629
886
|
if (paras.length === 0)
|
|
630
887
|
return null;
|
|
631
888
|
const p = paras.item(0);
|
|
632
|
-
return p
|
|
889
|
+
return getAttributeSafe(p, OOXML.W14_NS, 'paraId', 'w14', { bareFallback: false });
|
|
633
890
|
}
|
|
634
|
-
function removeCommentMarkersFromDocument(documentXml, commentId) {
|
|
891
|
+
function removeCommentMarkersFromDocument(documentXml, commentId, ctx) {
|
|
635
892
|
const cidStr = String(commentId);
|
|
636
893
|
// Remove commentRangeStart elements
|
|
637
894
|
const rangeStarts = documentXml.getElementsByTagNameNS(OOXML.W_NS, W.commentRangeStart);
|
|
638
895
|
const startsToRemove = [];
|
|
639
896
|
for (let i = 0; i < rangeStarts.length; i++) {
|
|
640
897
|
const el = rangeStarts.item(i);
|
|
641
|
-
const id = el
|
|
898
|
+
const id = getAttributeSafe(el, OOXML.W_NS, 'id', 'w', { bareFallback: false });
|
|
642
899
|
if (id === cidStr)
|
|
643
900
|
startsToRemove.push(el);
|
|
644
901
|
}
|
|
@@ -649,18 +906,19 @@ function removeCommentMarkersFromDocument(documentXml, commentId) {
|
|
|
649
906
|
const endsToRemove = [];
|
|
650
907
|
for (let i = 0; i < rangeEnds.length; i++) {
|
|
651
908
|
const el = rangeEnds.item(i);
|
|
652
|
-
const id = el
|
|
909
|
+
const id = getAttributeSafe(el, OOXML.W_NS, 'id', 'w', { bareFallback: false });
|
|
653
910
|
if (id === cidStr)
|
|
654
911
|
endsToRemove.push(el);
|
|
655
912
|
}
|
|
656
913
|
for (const el of endsToRemove)
|
|
657
914
|
el.parentNode?.removeChild(el);
|
|
658
|
-
// Remove commentReference elements
|
|
915
|
+
// Remove commentReference elements, or preserve their containing run under
|
|
916
|
+
// a deletion wrapper when tracked changes are requested.
|
|
659
917
|
const refs = documentXml.getElementsByTagNameNS(OOXML.W_NS, W.commentReference);
|
|
660
918
|
const refsToRemove = [];
|
|
661
919
|
for (let i = 0; i < refs.length; i++) {
|
|
662
920
|
const el = refs.item(i);
|
|
663
|
-
const id = el
|
|
921
|
+
const id = getAttributeSafe(el, OOXML.W_NS, 'id', 'w', { bareFallback: false });
|
|
664
922
|
if (id === cidStr)
|
|
665
923
|
refsToRemove.push(el);
|
|
666
924
|
}
|
|
@@ -668,10 +926,29 @@ function removeCommentMarkersFromDocument(documentXml, commentId) {
|
|
|
668
926
|
const run = ref.parentNode;
|
|
669
927
|
if (!run)
|
|
670
928
|
continue;
|
|
929
|
+
if (ctx) {
|
|
930
|
+
const parent = run.parentNode;
|
|
931
|
+
if (!parent)
|
|
932
|
+
continue;
|
|
933
|
+
const deletion = createRevisionContainer(documentXml, 'del', ctx);
|
|
934
|
+
parent.replaceChild(deletion, run);
|
|
935
|
+
deletion.appendChild(prepareElementForDeletion(run));
|
|
936
|
+
continue;
|
|
937
|
+
}
|
|
671
938
|
run.removeChild(ref);
|
|
672
939
|
// Remove run only if it has no visible content after removing the reference
|
|
673
940
|
if (!hasVisibleRunContent(run)) {
|
|
674
|
-
run.parentNode
|
|
941
|
+
const runParent = run.parentNode;
|
|
942
|
+
runParent?.removeChild(run);
|
|
943
|
+
// If the run lived inside a tracked-change wrapper (e.g., the comment
|
|
944
|
+
// was added with ctx earlier and is now being deleted without ctx),
|
|
945
|
+
// the wrapper is left orphaned with no content. Clean it up.
|
|
946
|
+
if (runParent && isW(runParent, 'ins')) {
|
|
947
|
+
runParent.parentNode?.removeChild(runParent);
|
|
948
|
+
}
|
|
949
|
+
else if (runParent && isW(runParent, 'del')) {
|
|
950
|
+
runParent.parentNode?.removeChild(runParent);
|
|
951
|
+
}
|
|
675
952
|
}
|
|
676
953
|
}
|
|
677
954
|
}
|