@docx4j/core-ts 0.5.0 → 0.7.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/NOTICE +4 -0
- package/README.md +92 -4
- package/dist/html/convert.mjs +2 -1
- package/dist/html/elements.mjs +2 -1
- package/dist/model/color/ColorMath.d.mts +29 -0
- package/dist/model/color/ColorMath.mjs +146 -0
- package/dist/model/color/ColorTables.d.mts +6 -0
- package/dist/model/color/ColorTables.mjs +83 -0
- package/dist/model/color/DrawingMLColors.d.mts +52 -0
- package/dist/model/color/DrawingMLColors.mjs +374 -0
- package/dist/model/color/ResolvedColor.d.mts +36 -0
- package/dist/model/color/ResolvedColor.mjs +65 -0
- package/dist/model/color/SpreadsheetColors.d.mts +36 -0
- package/dist/model/color/SpreadsheetColors.mjs +84 -0
- package/dist/model/color/StyleFill.d.mts +28 -0
- package/dist/model/color/StyleFill.mjs +21 -0
- package/dist/model/color/ThemeColors.d.mts +86 -0
- package/dist/model/color/ThemeColors.mjs +304 -0
- package/dist/model/color/WordColors.d.mts +71 -0
- package/dist/model/color/WordColors.mjs +121 -0
- package/dist/model/color/index.d.mts +6 -0
- package/dist/model/color/index.mjs +30 -0
- package/dist/model/content/Body.d.mts +145 -23
- package/dist/model/content/Body.mjs +507 -150
- package/dist/model/content/Comment.d.mts +23 -5
- package/dist/model/content/Comment.mjs +207 -26
- package/dist/model/content/ContentControl.d.mts +198 -19
- package/dist/model/content/ContentControl.mjs +672 -99
- package/dist/model/content/Font.d.mts +70 -17
- package/dist/model/content/Font.mjs +119 -36
- package/dist/model/content/InlinePicture.d.mts +11 -0
- package/dist/model/content/InlinePicture.mjs +21 -1
- package/dist/model/content/List.d.mts +6 -3
- package/dist/model/content/List.mjs +18 -8
- package/dist/model/content/Paragraph.d.mts +200 -14
- package/dist/model/content/Paragraph.mjs +898 -82
- package/dist/model/content/Range.d.mts +227 -12
- package/dist/model/content/Range.mjs +785 -59
- package/dist/model/content/ResolvedView.d.mts +104 -0
- package/dist/model/content/ResolvedView.mjs +596 -0
- package/dist/model/content/Table.d.mts +29 -3
- package/dist/model/content/Table.mjs +135 -49
- package/dist/model/content/TrackedChange.d.mts +174 -15
- package/dist/model/content/TrackedChange.mjs +734 -170
- package/dist/model/content/comments.d.mts +20 -6
- package/dist/model/content/comments.mjs +67 -57
- package/dist/model/content/generalException.d.mts +18 -0
- package/dist/model/content/generalException.mjs +24 -0
- package/dist/model/content/index.d.mts +10 -3
- package/dist/model/content/index.mjs +10 -2
- package/dist/model/content/mathText.d.mts +21 -0
- package/dist/model/content/mathText.mjs +267 -0
- package/dist/model/content/officeText.d.mts +27 -0
- package/dist/model/content/officeText.mjs +74 -0
- package/dist/model/content/ooxml.mjs +12 -1
- package/dist/model/content/outline.d.mts +5 -0
- package/dist/model/content/outline.mjs +13 -0
- package/dist/model/content/paraIds.d.mts +5 -0
- package/dist/model/content/paraIds.mjs +54 -0
- package/dist/model/content/placeholders.d.mts +110 -0
- package/dist/model/content/placeholders.mjs +253 -0
- package/dist/model/content/search.d.mts +74 -0
- package/dist/model/content/search.mjs +255 -9
- package/dist/model/content/spanEdits.d.mts +159 -0
- package/dist/model/content/spanEdits.mjs +728 -0
- package/dist/model/content/story.d.mts +47 -0
- package/dist/model/content/story.mjs +95 -0
- package/dist/model/content/tableColumns.d.mts +1 -1
- package/dist/model/content/tableColumns.mjs +108 -80
- package/dist/model/content/tableRevisions.d.mts +11 -3
- package/dist/model/content/tableRevisions.mjs +72 -42
- package/dist/model/content/tableRules.d.mts +30 -0
- package/dist/model/content/tableRules.mjs +102 -0
- package/dist/model/content/textBoxes.d.mts +18 -0
- package/dist/model/content/textBoxes.mjs +105 -0
- package/dist/model/content/trackedChangeFilter.d.mts +39 -0
- package/dist/model/content/trackedChangeFilter.mjs +81 -0
- package/dist/model/content/tracking.d.mts +63 -6
- package/dist/model/content/tracking.mjs +242 -22
- package/dist/model/content/tree.d.mts +172 -24
- package/dist/model/content/tree.mjs +328 -54
- package/dist/model/content/wordRange.d.mts +48 -0
- package/dist/model/content/wordRange.mjs +97 -0
- package/dist/model/customxml/CustomXmlPartCollection.d.mts +30 -1
- package/dist/model/customxml/CustomXmlPartCollection.mjs +81 -12
- package/dist/model/customxml/XmlMapping.d.mts +32 -8
- package/dist/model/customxml/XmlMapping.mjs +64 -10
- package/dist/model/customxml/bindings.d.mts +33 -4
- package/dist/model/customxml/bindings.mjs +171 -45
- package/dist/model/customxml/index.d.mts +1 -0
- package/dist/model/customxml/index.mjs +1 -0
- package/dist/model/customxml/kinds.d.mts +8 -3
- package/dist/model/customxml/kinds.mjs +26 -5
- package/dist/model/customxml/opendope.d.mts +4 -1
- package/dist/model/customxml/opendope.mjs +9 -6
- package/dist/model/customxml/picturePlaceholder.d.mts +17 -0
- package/dist/model/customxml/picturePlaceholder.mjs +32 -0
- package/dist/model/customxml/pictures.d.mts +4 -3
- package/dist/model/customxml/pictures.mjs +22 -8
- package/dist/model/customxml/storeItems.d.mts +57 -0
- package/dist/model/customxml/storeItems.mjs +122 -0
- package/dist/model/customxml/xhtml.mjs +23 -22
- package/dist/model/dateZone.d.mts +25 -0
- package/dist/model/dateZone.mjs +57 -0
- package/dist/model/index.d.mts +3 -0
- package/dist/model/index.mjs +5 -0
- package/dist/model/listnumbering/Emulator.d.mts +37 -5
- package/dist/model/listnumbering/Emulator.mjs +63 -9
- package/dist/model/properties/PropertyResolver.d.mts +74 -2
- package/dist/model/properties/PropertyResolver.mjs +133 -5
- package/dist/model/properties/catalogue.d.mts +19 -5
- package/dist/model/properties/catalogue.mjs +41 -15
- package/dist/model/properties/index.d.mts +1 -1
- package/dist/model/properties/log.d.mts +1 -1
- package/dist/model/properties/log.mjs +5 -5
- package/dist/model/properties/styleUtil.d.mts +3 -1
- package/dist/model/properties/styleUtil.mjs +20 -24
- package/dist/model/properties/table.mjs +4 -2
- package/dist/model/text/CaseMapping.d.mts +38 -0
- package/dist/model/text/CaseMapping.mjs +86 -0
- package/dist/model/text/Extractor.d.mts +349 -0
- package/dist/model/text/Extractor.mjs +2406 -0
- package/dist/model/text/LinkTargets.d.mts +45 -0
- package/dist/model/text/LinkTargets.mjs +170 -0
- package/dist/model/text/MarkdownExporter.d.mts +140 -0
- package/dist/model/text/MarkdownExporter.mjs +255 -0
- package/dist/model/text/PlainText.d.mts +29 -0
- package/dist/model/text/PlainText.mjs +542 -0
- package/dist/model/text/TextContext.d.mts +256 -0
- package/dist/model/text/TextContext.mjs +600 -0
- package/dist/model/text/TextOptions.d.mts +233 -0
- package/dist/model/text/TextOptions.mjs +419 -0
- package/dist/model/text/TextResult.d.mts +621 -0
- package/dist/model/text/TextResult.mjs +629 -0
- package/dist/model/text/Texts.d.mts +99 -0
- package/dist/model/text/Texts.mjs +187 -0
- package/dist/model/text/WmlToMarkdown.d.mts +120 -0
- package/dist/model/text/WmlToMarkdown.mjs +936 -0
- package/dist/model/text/caseMapping.generated.d.mts +7 -0
- package/dist/model/text/caseMapping.generated.mjs +214 -0
- package/dist/model/text/commonmark.d.mts +121 -0
- package/dist/model/text/commonmark.mjs +655 -0
- package/dist/model/text/index.d.mts +8 -0
- package/dist/model/text/index.mjs +9 -0
- package/dist/model/text/normalize.d.mts +8 -0
- package/dist/model/text/normalize.mjs +13 -0
- package/dist/model/text/outline.d.mts +4 -0
- package/dist/model/text/outline.mjs +71 -0
- package/dist/model/wordIntegers.d.mts +29 -0
- package/dist/model/wordIntegers.mjs +78 -0
- package/dist/office-js/document.mjs +15 -1
- package/dist/office-js/fragment.d.mts +7 -5
- package/dist/office-js/fragment.mjs +63 -14
- package/dist/office-js/proxy.d.mts +8 -0
- package/dist/office-js/proxy.mjs +29 -0
- package/dist/office-js/run.mjs +2 -0
- package/dist/office-js/supported.generated.mjs +16 -0
- package/dist/office-js/toApiScript.mjs +29 -12
- package/dist/opc/DirectoryPartStore.d.mts +6 -2
- package/dist/opc/DirectoryPartStore.mjs +11 -4
- package/dist/opc/FlatOpcPartStore.d.mts +4 -2
- package/dist/opc/FlatOpcPartStore.mjs +11 -8
- package/dist/opc/Load.d.mts +32 -0
- package/dist/opc/Load.mjs +115 -5
- package/dist/opc/PartStore.d.mts +34 -1
- package/dist/opc/PartStore.mjs +71 -8
- package/dist/opc/Save.d.mts +32 -0
- package/dist/opc/Save.mjs +110 -4
- package/dist/opc/ZipPartStore.d.mts +4 -2
- package/dist/opc/ZipPartStore.mjs +12 -7
- package/dist/opc/exceptions.d.mts +140 -0
- package/dist/opc/exceptions.mjs +108 -0
- package/dist/opc/index.d.mts +8 -5
- package/dist/opc/index.mjs +5 -3
- package/dist/opc/mce/preprocessor.d.mts +141 -5
- package/dist/opc/mce/preprocessor.mjs +119 -18
- package/dist/opc/mce/understood.d.mts +3 -11
- package/dist/opc/mce/understood.mjs +27 -23
- package/dist/opc/repair/breaks.d.mts +23 -0
- package/dist/opc/repair/breaks.mjs +288 -0
- package/dist/opc/repair/hosts.d.mts +12 -0
- package/dist/opc/repair/hosts.mjs +33 -0
- package/dist/opc/repair/mainRoot.d.mts +19 -0
- package/dist/opc/repair/mainRoot.mjs +168 -0
- package/dist/opc/repair/unknownElements.d.mts +8 -0
- package/dist/opc/repair/unknownElements.mjs +180 -0
- package/dist/opc/repair/wmlNames.generated.d.mts +2 -0
- package/dist/opc/repair/wmlNames.generated.mjs +80 -0
- package/dist/opc/skipped.d.mts +50 -0
- package/dist/opc/skipped.mjs +140 -0
- package/dist/packages/OpcPackage.d.mts +110 -4
- package/dist/packages/OpcPackage.mjs +123 -7
- package/dist/packages/WordprocessingMLPackage.d.mts +45 -8
- package/dist/packages/WordprocessingMLPackage.mjs +158 -21
- package/dist/packages/index.d.mts +1 -1
- package/dist/parts/DefaultXmlPart.mjs +2 -2
- package/dist/parts/Part.d.mts +14 -2
- package/dist/parts/RelationshipsPart.d.mts +15 -0
- package/dist/parts/RelationshipsPart.mjs +66 -1
- package/dist/parts/XmlPart.d.mts +63 -32
- package/dist/parts/XmlPart.mjs +240 -96
- package/dist/parts/wml/comments.mjs +30 -0
- package/dist/parts/wml/index.d.mts +12 -1
- package/dist/parts/wml/index.mjs +37 -1
- package/dist/xml/dom.d.mts +7 -0
- package/dist/xml/dom.mjs +18 -0
- package/package.json +2 -2
|
@@ -1,20 +1,35 @@
|
|
|
1
|
-
import { Font } from './Font.mjs';
|
|
1
|
+
import { Font, MixedFont, readingOf, markHolderOf } from './Font.mjs';
|
|
2
2
|
import { runFontSelectorOf } from '../fonts/lookup.mjs';
|
|
3
3
|
import { typeNameOf, linkParents, runItemsOf, runsOf, revisionKindOf, WHOLE } from './tree.mjs';
|
|
4
4
|
import { Docx4JException } from '../../opc/exceptions.mjs';
|
|
5
|
-
import {
|
|
5
|
+
import { NotSupportedError } from '../../office-js/errors.mjs';
|
|
6
|
+
import { ContentControl, assertControlHolds, controlsOfTypes, showsPlaceholderWhenMade, BLOCK_CONTROL } from './ContentControl.mjs';
|
|
7
|
+
import { showPicturePlaceholder } from '../customxml/picturePlaceholder.mjs';
|
|
6
8
|
import { sdt as sdtOf, nextSdtId, applyRunOptions, t as textElement } from '@docx4j/generated-objects-ts/builders/wml';
|
|
7
9
|
import { hyperlink as hyperlinkOf } from '@docx4j/generated-objects-ts/el/org_docx4j_wml';
|
|
8
10
|
import { Namespaces } from '../../parts/Namespaces.mjs';
|
|
9
11
|
import { controlIdScope, sdtKindFor, declareSdtExtensions } from '../customxml/insert.mjs';
|
|
10
12
|
import { contentOf } from './ooxml.mjs';
|
|
11
13
|
import { markHolderInserted } from './tracking.mjs';
|
|
12
|
-
import {
|
|
14
|
+
import { officeMatches, wildcardMatchesAcross, refuseWildcardMarkReplacement, expandReplacement, paragraphMarkPieces, refuseMarkReplacement } from './search.mjs';
|
|
13
15
|
import { commentApi } from './comments.mjs';
|
|
16
|
+
import { storyLayout, storyOf } from './story.mjs';
|
|
17
|
+
import { styleNameOf, builtInOf } from './styles.mjs';
|
|
18
|
+
import { GeneralException } from './generalException.mjs';
|
|
19
|
+
import { extentOf, assertTrackable, deleteSpan, firstCharacterOf, placeReplacement, insertParagraphAt, formatSpan } from './spanEdits.mjs';
|
|
14
20
|
/**
|
|
15
|
-
* A subset of Office JS `Word.Range`: a span of text
|
|
16
|
-
* paragraph
|
|
17
|
-
*
|
|
21
|
+
* A subset of Office JS `Word.Range`: a span of text from an offset in one paragraph to an offset in the same
|
|
22
|
+
* paragraph or a later one of the same story, [start, end), `start` in `paragraph`'s text and `end` in
|
|
23
|
+
* `endParagraph`'s. Positions are recomputed from the tree on every use, so a range stays valid across edits
|
|
24
|
+
* made through it; edits made elsewhere may shift it.
|
|
25
|
+
*
|
|
26
|
+
* A range within one paragraph is what it has been since CR-002: `endParagraph` is `paragraph`. A range across
|
|
27
|
+
* paragraphs (CR-014 phase 1) reads as Office JS's does (check 52): its `text` in `body.text`'s form between
|
|
28
|
+
* its ends, `paragraphs` every paragraph it touches (a table's cells' among them), `font` `null` where the runs
|
|
29
|
+
* differ, `search`, the tracked changes and the comments over all of it. Its writes (phase 2) are Word's too:
|
|
30
|
+
* `delete` and `insertText('Replace')` join the paragraphs in the last one's properties, `font` formats every
|
|
31
|
+
* run and mark in it, `insertText` goes at its ends, and `insertParagraph` splits the paragraph at its start or
|
|
32
|
+
* end; the rest of its writes throw `NotSupportedError`.
|
|
18
33
|
*
|
|
19
34
|
* A picture has a place in the text and no text of its own, so a picture at the range's start or
|
|
20
35
|
* end offset may be inside it or outside it. `whole` says: a range from `Paragraph.getRange('Whole')`
|
|
@@ -23,38 +38,325 @@ import { commentApi } from './comments.mjs';
|
|
|
23
38
|
* hit replaced does not take the picture beside it (CR-002 section 48).
|
|
24
39
|
*/
|
|
25
40
|
export class Range {
|
|
26
|
-
constructor(paragraph, start, end, whole = false
|
|
27
|
-
|
|
41
|
+
constructor(paragraph, start, end, whole = false,
|
|
42
|
+
/**
|
|
43
|
+
* A paragraph's 'Whole' range: its text ends with the paragraph's mark, as Office JS's does (check 48:
|
|
44
|
+
* `\r`, and `\t` for a cell's last paragraph, the cell's end), where 'Content' ends with its text.
|
|
45
|
+
* The mark is no offset: every edit and search works on the text before it.
|
|
46
|
+
*/
|
|
47
|
+
mark = false,
|
|
48
|
+
/** The paragraph holding `end`, where it isn't `paragraph` (CR-014 phase 1). */
|
|
49
|
+
endParagraph,
|
|
50
|
+
/**
|
|
51
|
+
* What the range's text ends with in place of `endParagraph`'s mark, where `mark` is set: a table's
|
|
52
|
+
* 'Whole' range ends with its last row's end, `\r\n` (check 52).
|
|
53
|
+
*/
|
|
54
|
+
endMark) {
|
|
55
|
+
this.start = start;
|
|
56
|
+
this.end = end;
|
|
57
|
+
this.startParagraph = paragraph;
|
|
58
|
+
this.lastParagraph = endParagraph !== null && endParagraph !== void 0 ? endParagraph : paragraph;
|
|
59
|
+
this.holdsWhole = whole;
|
|
60
|
+
this.holdsMark = mark;
|
|
61
|
+
this.lastMarkText = endMark;
|
|
62
|
+
}
|
|
63
|
+
/** @internal Marks this range as its paragraph's End or Content range (`Paragraph.getRange`); returns it. */
|
|
64
|
+
atEndOf(kind) {
|
|
65
|
+
this.endOf = kind;
|
|
66
|
+
return this;
|
|
67
|
+
}
|
|
68
|
+
/** Whether text put at `location` is written at the paragraph's End: its End range's, at any location; its Content range's End and After. */
|
|
69
|
+
writesAtParagraphEnd(location) {
|
|
70
|
+
if (!this.endOf || this.spansParagraphs || this.end !== this.paragraph.text.length)
|
|
71
|
+
return false;
|
|
72
|
+
return this.endOf === 'End' ? this.start === this.end : location === 'End' || location === 'After';
|
|
73
|
+
}
|
|
74
|
+
/** The paragraph holding `start`. */
|
|
75
|
+
get paragraph() {
|
|
76
|
+
return this.startParagraph;
|
|
77
|
+
}
|
|
78
|
+
/** The paragraph holding `end`: `paragraph` for a range within one paragraph (CR-014 phase 1). */
|
|
79
|
+
get endParagraph() {
|
|
80
|
+
return this.lastParagraph;
|
|
81
|
+
}
|
|
82
|
+
/** The range holds its paragraphs' whole content, the pictures at its edges too (CR-002 section 48). */
|
|
83
|
+
get whole() {
|
|
84
|
+
return this.holdsWhole;
|
|
85
|
+
}
|
|
86
|
+
/** The range's text ends with its last paragraph's mark: a 'Whole' range. */
|
|
87
|
+
get mark() {
|
|
88
|
+
return this.holdsMark;
|
|
89
|
+
}
|
|
90
|
+
/** What the text ends with in place of the last paragraph's mark: a table's 'Whole' range's `\r\n`. */
|
|
91
|
+
get endMark() {
|
|
92
|
+
return this.lastMarkText;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* True where the range holds its last paragraph's mark (a 'Whole' range ending at the paragraph's end), or a
|
|
96
|
+
* table's end in its place: what `delete` takes and `Replace` keeps (check 52, W1 and W2). An extension.
|
|
97
|
+
*/
|
|
98
|
+
get holdsEndMark() {
|
|
99
|
+
return this.holdsMark && this.end >= this.lastParagraph.text.length;
|
|
100
|
+
}
|
|
101
|
+
/** The range collapsed where an edit through it closed it up, or over what the edit put there. */
|
|
102
|
+
/**
|
|
103
|
+
* This range moved onto another's span: its paragraphs and offsets (a replacement's, CR-002 section 60); `fresh`, it
|
|
104
|
+
* no longer holds a mark or a paragraph's whole content either, as `landAt` leaves one.
|
|
105
|
+
*/
|
|
106
|
+
cover(r, fresh = false) {
|
|
107
|
+
this.startParagraph = r.paragraph;
|
|
108
|
+
this.lastParagraph = r.endParagraph;
|
|
109
|
+
this.start = r.start;
|
|
110
|
+
this.end = r.end;
|
|
111
|
+
if (fresh) {
|
|
112
|
+
this.holdsWhole = false;
|
|
113
|
+
this.holdsMark = false;
|
|
114
|
+
this.lastMarkText = undefined;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
landAt(paragraph, start, end = start) {
|
|
118
|
+
this.startParagraph = paragraph;
|
|
119
|
+
this.lastParagraph = paragraph;
|
|
28
120
|
this.start = start;
|
|
29
121
|
this.end = end;
|
|
30
|
-
this.
|
|
122
|
+
this.holdsWhole = false;
|
|
123
|
+
this.holdsMark = false;
|
|
124
|
+
this.lastMarkText = undefined;
|
|
125
|
+
}
|
|
126
|
+
/** True for a range across paragraphs: its ends are in two (extension; CR-014). */
|
|
127
|
+
get spansParagraphs() {
|
|
128
|
+
return this.endParagraph.element !== this.paragraph.element;
|
|
31
129
|
}
|
|
32
130
|
/** The edges `Paragraph.splice` is given: both for a whole range. */
|
|
33
131
|
get edges() {
|
|
34
132
|
return this.whole ? WHOLE : undefined;
|
|
35
133
|
}
|
|
134
|
+
/** The story's layout, and where this range's two paragraphs are in it. */
|
|
135
|
+
located() {
|
|
136
|
+
const layout = storyLayout(storyOf(this.paragraph.p).items, (element, container) => this.viewOf(element, container).text);
|
|
137
|
+
const first = layout.indexOf(this.paragraph.p);
|
|
138
|
+
const last = layout.indexOf(this.endParagraph.p);
|
|
139
|
+
if (first < 0 || last < 0)
|
|
140
|
+
throw new Docx4JException('This range\'s paragraphs are not in one story as a reader reads it');
|
|
141
|
+
return { layout, first, last };
|
|
142
|
+
}
|
|
143
|
+
/** The view of a paragraph of this range's story: this range's own where it is one of its two. */
|
|
144
|
+
viewOf(element, container) {
|
|
145
|
+
if (element === this.paragraph.element)
|
|
146
|
+
return this.paragraph;
|
|
147
|
+
if (element === this.endParagraph.element)
|
|
148
|
+
return this.endParagraph;
|
|
149
|
+
return this.paragraph.viewOf(element, container);
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* The range as one range per paragraph it touches: its part of the first, the whole of each between, its
|
|
153
|
+
* part of the last. A range within one paragraph is itself.
|
|
154
|
+
*/
|
|
155
|
+
pieces() {
|
|
156
|
+
if (!this.spansParagraphs)
|
|
157
|
+
return [this];
|
|
158
|
+
const { layout, first, last } = this.located();
|
|
159
|
+
return layout.paragraphs.slice(first, last + 1).map((sp, i, all) => {
|
|
160
|
+
const p = this.viewOf(sp.element, sp.container);
|
|
161
|
+
if (i === 0)
|
|
162
|
+
return new Range(p, this.start, p.text.length, this.whole);
|
|
163
|
+
if (i === all.length - 1)
|
|
164
|
+
return new Range(p, 0, this.end, this.whole);
|
|
165
|
+
return new Range(p, 0, p.text.length, true);
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Refuses a hyperlink set on a range across paragraphs. Word wrote a HYPERLINK field from the first paragraph into
|
|
170
|
+
* the last (check 54 case 34), where this package's links are `w:hyperlink` elements, one paragraph's each, which
|
|
171
|
+
* its getter reads (CR-014 section 12).
|
|
172
|
+
*/
|
|
173
|
+
assertOneParagraph(member) {
|
|
174
|
+
if (this.spansParagraphs) {
|
|
175
|
+
throw new NotSupportedError('Range', member, "this range spans paragraphs, where Word writes a HYPERLINK field across them, which isn't "
|
|
176
|
+
+ "written here (CR-014); link each paragraph's range");
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
/** Refuses, before anything changes, a write over a paragraph of the range that is read-only (a text box's, a branch's). */
|
|
180
|
+
assertWritableSpan() {
|
|
181
|
+
for (const p of this.paragraphs)
|
|
182
|
+
p.assertWritable();
|
|
183
|
+
}
|
|
184
|
+
/** Every paragraph the range touches, in document order, a table's cells' among them (check 52). */
|
|
36
185
|
get paragraphs() {
|
|
37
|
-
|
|
186
|
+
if (!this.spansParagraphs)
|
|
187
|
+
return [this.paragraph];
|
|
188
|
+
const { layout, first, last } = this.located();
|
|
189
|
+
return layout.paragraphs.slice(first, last + 1).map((sp) => this.viewOf(sp.element, sp.container));
|
|
38
190
|
}
|
|
191
|
+
/**
|
|
192
|
+
* The range's text, in `body.text`'s form across paragraphs (check 52): a mark `\r` between paragraphs, a
|
|
193
|
+
* table's cells a `\t` apart and each row ended `\r\n`; with the last paragraph's mark where the range
|
|
194
|
+
* holds it (a 'Whole' range).
|
|
195
|
+
*/
|
|
39
196
|
get text() {
|
|
40
|
-
|
|
197
|
+
var _a, _b;
|
|
198
|
+
if (!this.spansParagraphs) {
|
|
199
|
+
const text = this.paragraph.text;
|
|
200
|
+
const span = text.substring(this.start, this.end);
|
|
201
|
+
return this.mark && this.end >= text.length ? span + ((_a = this.endMark) !== null && _a !== void 0 ? _a : this.paragraph.markText) : span;
|
|
202
|
+
}
|
|
203
|
+
const tail = this.mark && this.end >= this.endParagraph.text.length ? (_b = this.endMark) !== null && _b !== void 0 ? _b : this.endParagraph.markText : '';
|
|
204
|
+
const { layout, first, last } = this.located();
|
|
205
|
+
return layout.text.slice(layout.paragraphs[first].start + this.start, layout.paragraphs[last].start + this.end) + tail;
|
|
41
206
|
}
|
|
42
207
|
set text(value) {
|
|
43
208
|
this.insertText(value, 'Replace');
|
|
44
209
|
}
|
|
45
|
-
/**
|
|
210
|
+
/** True when the range holds no text (Office JS `isEmpty`). */
|
|
211
|
+
get isEmpty() {
|
|
212
|
+
return this.text === '';
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* The paragraph's style id (extension, as on Paragraph); a range across paragraphs gives the first one's. Set
|
|
216
|
+
* across paragraphs, each paragraph's (check 54 cases 32 and 33: Word gave both Heading 2).
|
|
217
|
+
*/
|
|
46
218
|
get styleId() {
|
|
47
219
|
return this.paragraph.styleId;
|
|
48
220
|
}
|
|
49
221
|
set styleId(id) {
|
|
50
|
-
this.
|
|
222
|
+
for (const p of this.styledParagraphs())
|
|
223
|
+
p.styleId = id;
|
|
51
224
|
}
|
|
52
|
-
/**
|
|
225
|
+
/**
|
|
226
|
+
* The range's style name, as Office JS reads it (checks 52 and 53). A range holding its paragraphs whole reads
|
|
227
|
+
* their paragraph style, where they share one, else `Normal`. Any other range reads its runs' character style
|
|
228
|
+
* where they share one (`Emphasis`), `''` where their character styles differ (some runs styled, some not: a
|
|
229
|
+
* footnote reference among plain text), and the paragraph style where none has one. Setting it sets the
|
|
230
|
+
* paragraph's style.
|
|
231
|
+
*/
|
|
53
232
|
get style() {
|
|
54
|
-
|
|
233
|
+
const read = this.styleRead();
|
|
234
|
+
return read.charStyle === undefined ? read.paragraphs((p) => p.style) : read.charStyle === '' ? '' : styleNameOf(this.paragraph.parentBody.package_, read.charStyle);
|
|
55
235
|
}
|
|
56
236
|
set style(id) {
|
|
57
|
-
this.
|
|
237
|
+
for (const p of this.styledParagraphs())
|
|
238
|
+
p.style = id;
|
|
239
|
+
}
|
|
240
|
+
/** The built-in name of `style` (Office JS `styleBuiltIn`): `Other` where it is `''`, `Normal` where paragraphs across it differ. */
|
|
241
|
+
get styleBuiltIn() {
|
|
242
|
+
const read = this.styleRead();
|
|
243
|
+
return read.charStyle === undefined ? read.paragraphs((p) => p.styleBuiltIn) : read.charStyle === '' ? 'Other' : builtInOf(read.charStyle);
|
|
244
|
+
}
|
|
245
|
+
/** Sets each paragraph's style by its built-in name, as `style` does. */
|
|
246
|
+
set styleBuiltIn(value) {
|
|
247
|
+
for (const p of this.styledParagraphs())
|
|
248
|
+
p.styleBuiltIn = value;
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* The paragraphs a style set on the range goes to: its own, or each across paragraphs (check 54 cases 32 and 33:
|
|
252
|
+
* Word gave a body paragraph and a list item Heading 2). Word also took away the body paragraph's centring and
|
|
253
|
+
* italic and the list item's numbering, which `Paragraph.style` doesn't (CR-014 section 12).
|
|
254
|
+
*/
|
|
255
|
+
styledParagraphs() {
|
|
256
|
+
if (!this.spansParagraphs)
|
|
257
|
+
return [this.paragraph];
|
|
258
|
+
this.assertWritableSpan();
|
|
259
|
+
return this.paragraphs;
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* What `style` reads: the runs' shared character style id, `''` where their character styles differ, or
|
|
263
|
+
* undefined where the paragraph style applies, with how to read that.
|
|
264
|
+
*/
|
|
265
|
+
styleRead() {
|
|
266
|
+
const paragraphs = (read) => {
|
|
267
|
+
if (!this.spansParagraphs)
|
|
268
|
+
return read(this.paragraph);
|
|
269
|
+
const values = new Set(this.paragraphs.map(read));
|
|
270
|
+
return values.size === 1 ? [...values][0] : 'Normal';
|
|
271
|
+
};
|
|
272
|
+
const holdsWhole = this.start === 0 && this.end >= this.endParagraph.text.length;
|
|
273
|
+
if (holdsWhole)
|
|
274
|
+
return { charStyle: undefined, paragraphs };
|
|
275
|
+
const ids = new Set(this.runs.map((run) => { var _a, _b, _c; return (_c = (_b = (_a = run.value.rPr) === null || _a === void 0 ? void 0 : _a.rStyle) === null || _b === void 0 ? void 0 : _b.val) !== null && _c !== void 0 ? _c : ''; }));
|
|
276
|
+
if (ids.size === 0)
|
|
277
|
+
return { charStyle: undefined, paragraphs };
|
|
278
|
+
if (ids.size > 1)
|
|
279
|
+
return { charStyle: '', paragraphs };
|
|
280
|
+
const only = [...ids][0];
|
|
281
|
+
return { charStyle: only === '' ? undefined : only, paragraphs };
|
|
282
|
+
}
|
|
283
|
+
/** The table cell holding the range's start, if it is in one (Office JS `parentTableCell`, which throws instead). */
|
|
284
|
+
get parentTableCell() {
|
|
285
|
+
return this.paragraph.parentTableCell;
|
|
286
|
+
}
|
|
287
|
+
/** The table holding the range's start, if it is in one (Office JS `parentTable`, which throws instead). */
|
|
288
|
+
get parentTable() {
|
|
289
|
+
var _a;
|
|
290
|
+
return (_a = this.paragraph.parentTableCell) === null || _a === void 0 ? void 0 : _a.parentTable;
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* The tables in the range (Office JS `Range.tables`): a table the range covers some of, unless the range is
|
|
294
|
+
* inside one of its cells, where that cell's tables are looked at instead (check 52: a range across a table,
|
|
295
|
+
* or into or out of one, lists it; a cell's whole range lists none).
|
|
296
|
+
*/
|
|
297
|
+
get tables() {
|
|
298
|
+
const inRange = new Set(this.paragraphs.map((p) => p.element.value));
|
|
299
|
+
const pick = (tables) => tables.flatMap((table) => {
|
|
300
|
+
const cells = table.rows.flatMap((row) => row.cells);
|
|
301
|
+
if (!cells.some((cell) => cell.paragraphs.some((p) => inRange.has(p.p))))
|
|
302
|
+
return [];
|
|
303
|
+
const home = cells.find((cell) => [...inRange].every((p) => cell.paragraphs.some((cp) => cp.p === p)));
|
|
304
|
+
return home ? pick(home.body.tables) : [table];
|
|
305
|
+
});
|
|
306
|
+
return pick(this.storyBody().tables);
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* The content controls in the range (Office JS `Range.contentControls`): each control the range overlaps,
|
|
310
|
+
* except one holding the whole range, in document order (check 53: a range from a paragraph into a control
|
|
311
|
+
* lists it, one inside a control, its content whole, doesn't, one over an inline control lists it).
|
|
312
|
+
*/
|
|
313
|
+
get contentControls() {
|
|
314
|
+
const layout = storyLayout(storyOf(this.paragraph.p).items, (element, container) => this.viewOf(element, container).text);
|
|
315
|
+
const at = (p, offset) => {
|
|
316
|
+
const i = layout.indexOf(p.p);
|
|
317
|
+
return i < 0 ? Number.NaN : layout.paragraphs[i].start + offset;
|
|
318
|
+
};
|
|
319
|
+
const from = at(this.paragraph, this.start);
|
|
320
|
+
const to = at(this.endParagraph, this.end);
|
|
321
|
+
return this.storyBody().contentControls.filter((control) => {
|
|
322
|
+
let start;
|
|
323
|
+
let end;
|
|
324
|
+
if (control.form === 'Run') {
|
|
325
|
+
let range;
|
|
326
|
+
try {
|
|
327
|
+
range = control.getRange();
|
|
328
|
+
}
|
|
329
|
+
catch {
|
|
330
|
+
return false;
|
|
331
|
+
}
|
|
332
|
+
[start, end] = [at(range.paragraph, range.start), at(range.paragraph, range.end)];
|
|
333
|
+
}
|
|
334
|
+
else {
|
|
335
|
+
const paragraphs = control.paragraphs;
|
|
336
|
+
if (paragraphs.length === 0)
|
|
337
|
+
return false;
|
|
338
|
+
const last = paragraphs[paragraphs.length - 1];
|
|
339
|
+
[start, end] = [at(paragraphs[0], 0), at(last, last.text.length)];
|
|
340
|
+
}
|
|
341
|
+
if (Number.isNaN(start) || Number.isNaN(end))
|
|
342
|
+
return false;
|
|
343
|
+
const overlaps = start < to && end > from || (start === end && from <= start && start <= to);
|
|
344
|
+
const holdsRange = start <= from && to <= end;
|
|
345
|
+
return overlaps && !holdsRange;
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Office JS `getContentControls` (WordApi 1.5): the content controls `contentControls` lists, those of `options.types`
|
|
350
|
+
* where given. Word's `contentControls` lists rich-text controls only; this package's lists every kind, a recorded
|
|
351
|
+
* departure (CR-002 section 60 G1, check 55b), so this is how add-in code that asks for plain-text controls finds them.
|
|
352
|
+
*/
|
|
353
|
+
getContentControls(options) {
|
|
354
|
+
return controlsOfTypes(this.contentControls, options);
|
|
355
|
+
}
|
|
356
|
+
/** A body over this range's story, for its tables and controls. */
|
|
357
|
+
storyBody() {
|
|
358
|
+
const { owner } = storyOf(this.paragraph.p);
|
|
359
|
+
return this.paragraph.parentBody.sub(owner);
|
|
58
360
|
}
|
|
59
361
|
/**
|
|
60
362
|
* The text in one of the two views (extension; `text` is the accepted one). `{ view: 'original' }`
|
|
@@ -76,23 +378,52 @@ export class Range {
|
|
|
76
378
|
}
|
|
77
379
|
/** `font`, with `{ direct: true }` for the direct formatting of the first run (extension). */
|
|
78
380
|
getFont(options) {
|
|
79
|
-
|
|
381
|
+
// across paragraphs: each run read in its own paragraph, null where they differ, as Office JS's (check 52)
|
|
382
|
+
if (this.spansParagraphs) {
|
|
383
|
+
return new MixedFont(() => {
|
|
384
|
+
const resolver = this.paragraph.parentBody.propertyResolver;
|
|
385
|
+
const selector = runFontSelectorOf(this.paragraph.parentBody.package_);
|
|
386
|
+
const themeColors = (options === null || options === void 0 ? void 0 : options.direct) === true ? undefined : resolver.getThemeColors();
|
|
387
|
+
return this.pieces().flatMap((piece) => piece.runs.map((run) => {
|
|
388
|
+
const rPr = (options === null || options === void 0 ? void 0 : options.direct) === true ? run.value.rPr
|
|
389
|
+
: resolver.getEffectiveRPr(run.value.rPr, piece.paragraph.p.pPr, piece.paragraph.cellContext);
|
|
390
|
+
return readingOf(rPr, (options === null || options === void 0 ? void 0 : options.direct) !== true, (options === null || options === void 0 ? void 0 : options.direct) === true ? undefined : selector === null || selector === void 0 ? void 0 : selector.asciiFontName(rPr), themeColors);
|
|
391
|
+
}));
|
|
392
|
+
}, (opts) => {
|
|
393
|
+
// every run in it and every mark it holds (CR-014 phase 2, check 52)
|
|
394
|
+
this.assertWritableSpan();
|
|
395
|
+
const pieces = this.pieces();
|
|
396
|
+
formatSpan(pieces.map((piece, i) => ({
|
|
397
|
+
paragraph: piece.paragraph, start: piece.start, end: piece.end, runs: () => piece.runs,
|
|
398
|
+
holdsMark: i < pieces.length - 1 || this.holdsEndMark,
|
|
399
|
+
})), opts);
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
const holders = (write) => {
|
|
80
403
|
if (this.start === this.end)
|
|
81
404
|
return [];
|
|
82
|
-
|
|
83
|
-
|
|
405
|
+
// a write formats runs holding the span and nothing else: a tab, a break or text after the span in
|
|
406
|
+
// the same run is split off too, where splitting at the two offsets left it in the run and formatted
|
|
407
|
+
// it; a read splits nothing, the first run's properties being the same either way (CR-012 phase 4)
|
|
408
|
+
if (write)
|
|
409
|
+
this.paragraph.splitRunsAround(this.start, this.end);
|
|
84
410
|
return this.runs.map((r) => r.value);
|
|
85
411
|
};
|
|
412
|
+
// a range holding its paragraph's mark and no run, an empty paragraph's whole range, reads the mark (CR-002 section 59)
|
|
413
|
+
const mark = () => (this.holdsEndMark ? markHolderOf(this.paragraph.p) : undefined);
|
|
86
414
|
if ((options === null || options === void 0 ? void 0 : options.direct) === true)
|
|
87
|
-
return new Font(holders, () => this.paragraph.fontTracking());
|
|
88
|
-
return new Font(holders, () => this.paragraph.fontTracking(), (rPr) => this.paragraph.parentBody.propertyResolver.getEffectiveRPr(rPr, this.paragraph.p.pPr, this.paragraph.cellContext), (rPr) => { var _a; return (_a = runFontSelectorOf(this.paragraph.parentBody.package_)) === null || _a === void 0 ? void 0 : _a.asciiFontName(rPr); });
|
|
415
|
+
return new Font(holders, () => this.paragraph.fontTracking(), undefined, undefined, mark);
|
|
416
|
+
return new Font(holders, () => this.paragraph.fontTracking(), (rPr) => this.paragraph.parentBody.propertyResolver.getEffectiveRPr(rPr, this.paragraph.p.pPr, this.paragraph.cellContext), (rPr) => { var _a; return (_a = runFontSelectorOf(this.paragraph.parentBody.package_)) === null || _a === void 0 ? void 0 : _a.asciiFontName(rPr); }, mark, () => this.paragraph.parentBody.propertyResolver.getThemeColors());
|
|
89
417
|
}
|
|
90
418
|
/** The runs the span covers (a run partly inside counts). */
|
|
91
419
|
get runs() {
|
|
420
|
+
if (this.spansParagraphs)
|
|
421
|
+
return this.pieces().flatMap((piece) => piece.runs);
|
|
92
422
|
const out = [];
|
|
93
423
|
const seen = new Set();
|
|
94
424
|
for (const seg of this.paragraph.segments()) {
|
|
95
|
-
|
|
425
|
+
// an equation is in no run, and its math runs' formatting is their own (CR-012 phase 4)
|
|
426
|
+
if (seg.end <= this.start || seg.start >= this.end || seg.math)
|
|
96
427
|
continue;
|
|
97
428
|
if (seen.has(seg.run))
|
|
98
429
|
continue;
|
|
@@ -101,7 +432,45 @@ export class Range {
|
|
|
101
432
|
}
|
|
102
433
|
return out;
|
|
103
434
|
}
|
|
435
|
+
/**
|
|
436
|
+
* Text put at the range's start ('Start', 'Before'), at its end ('End', 'After'), or in its place ('Replace').
|
|
437
|
+
* Across paragraphs (CR-014 phase 2, check 52) the start is in the first paragraph and the end in the last,
|
|
438
|
+
* before a last mark the range holds, and a replacement is `delete()` keeping a last mark, then the text where the
|
|
439
|
+
* paragraphs joined: untracked, in the first replaced character's look (`lookOf`); tracked, a `w:ins` before the
|
|
440
|
+
* deletion, as Word writes it. A range holding its paragraph's mark is replaced so too under tracking.
|
|
441
|
+
*/
|
|
104
442
|
insertText(text, location) {
|
|
443
|
+
var _a;
|
|
444
|
+
// a control's own range: into a control showing its placeholder, or an empty run-level one, its writes go inside it,
|
|
445
|
+
// and a Replace over a whole run-level control showing its placeholder takes it (CR-002 section 63, D3 and D4)
|
|
446
|
+
const through = (_a = this.ofControl) === null || _a === void 0 ? void 0 : _a.control.writeThrough(this.ofControl.part, text, location);
|
|
447
|
+
if (through)
|
|
448
|
+
return through;
|
|
449
|
+
// a paragraph's End range, at any location, and its Content range's End and After: after everything the paragraph
|
|
450
|
+
// holds, as Word writes there (CR-002 sections 63.24 to 63.27, D12 to D14; checks 69 and 70)
|
|
451
|
+
if (this.writesAtParagraphEnd(location)) {
|
|
452
|
+
const r = this.paragraph.writeAtEnd(text, location === 'After');
|
|
453
|
+
if (location === 'Replace')
|
|
454
|
+
this.cover(r);
|
|
455
|
+
else if (location === 'Before' || location === 'Start') {
|
|
456
|
+
this.startParagraph = this.lastParagraph = r.endParagraph; // a line break may have made paragraphs
|
|
457
|
+
this.start = this.end = r.end;
|
|
458
|
+
}
|
|
459
|
+
return r;
|
|
460
|
+
}
|
|
461
|
+
// tracked, the w:ins goes before the w:del within one paragraph too, as Word's (check 54 case 4; Jason,
|
|
462
|
+
// 2026-10-23: "follow Word (insertion first)"), where CR-002 wrote the w:del first
|
|
463
|
+
if (location === 'Replace' && (this.spansParagraphs || this.paragraph.changeTracker))
|
|
464
|
+
return this.replaceAcross(text);
|
|
465
|
+
if (this.spansParagraphs) {
|
|
466
|
+
this.assertWritableSpan();
|
|
467
|
+
if (location === 'End' || location === 'After')
|
|
468
|
+
return this.endParagraph.splice(this.end, this.end, text);
|
|
469
|
+
const r = this.paragraph.splice(this.start, this.start, text, 'after');
|
|
470
|
+
this.startParagraph = r.endParagraph; // the text may have broken the paragraph (CR-002 section 60 E1)
|
|
471
|
+
this.start = r.end;
|
|
472
|
+
return r;
|
|
473
|
+
}
|
|
105
474
|
let r;
|
|
106
475
|
if (location === 'Replace')
|
|
107
476
|
r = this.paragraph.splice(this.start, this.end, text, 'before', this.edges);
|
|
@@ -111,26 +480,57 @@ export class Range {
|
|
|
111
480
|
r = this.paragraph.splice(this.start, this.start, text, 'after');
|
|
112
481
|
else
|
|
113
482
|
r = this.paragraph.splice(this.end, this.end, text);
|
|
114
|
-
// this range keeps covering the same text: a replacement resizes it, text before it shifts it, text after it leaves
|
|
483
|
+
// this range keeps covering the same text: a replacement resizes it, text before it shifts it, text after it leaves
|
|
484
|
+
// it; what was put in is the returned range's, which a line break may have taken into the paragraphs it made
|
|
115
485
|
if (location === 'Replace')
|
|
116
|
-
this.
|
|
486
|
+
this.cover(r);
|
|
117
487
|
else if (location === 'Before' || location === 'Start') {
|
|
118
|
-
this.
|
|
119
|
-
this.
|
|
488
|
+
const length = this.end - this.start;
|
|
489
|
+
this.startParagraph = this.lastParagraph = r.endParagraph;
|
|
490
|
+
this.start = r.end;
|
|
491
|
+
this.end = r.end + length;
|
|
120
492
|
}
|
|
121
493
|
return r;
|
|
122
494
|
}
|
|
495
|
+
/**
|
|
496
|
+
* A new paragraph of the text where the range starts ('Before') or ends ('After'), splitting the paragraph there,
|
|
497
|
+
* as Word does (`insertParagraphAt`): across paragraphs (check 52) and within one (check 54 cases 1 to 3; until
|
|
498
|
+
* then a range within one paragraph put it before or after the whole paragraph). At a paragraph's start or end,
|
|
499
|
+
* or after a range holding its mark, it goes before or after the paragraph. This range keeps covering its text.
|
|
500
|
+
*/
|
|
123
501
|
insertParagraph(text, location) {
|
|
124
|
-
|
|
502
|
+
this.assertWritableSpan();
|
|
503
|
+
const spans = this.spansParagraphs;
|
|
504
|
+
if (location === 'Before') {
|
|
505
|
+
const { inserted, firstHalf } = insertParagraphAt(this.paragraph, this.start, text);
|
|
506
|
+
if (firstHalf) { // the paragraph keeps the text from the split on
|
|
507
|
+
if (!spans)
|
|
508
|
+
this.end -= this.start;
|
|
509
|
+
this.start = 0;
|
|
510
|
+
}
|
|
511
|
+
return inserted;
|
|
512
|
+
}
|
|
513
|
+
const at = this.holdsEndMark ? this.endParagraph.text.length : this.end;
|
|
514
|
+
const { inserted, firstHalf } = insertParagraphAt(this.endParagraph, at, text);
|
|
515
|
+
if (firstHalf) { // the text before the split moved into the first half
|
|
516
|
+
if (!spans)
|
|
517
|
+
this.startParagraph = firstHalf;
|
|
518
|
+
this.lastParagraph = firstHalf;
|
|
519
|
+
}
|
|
520
|
+
return inserted;
|
|
125
521
|
}
|
|
126
522
|
/**
|
|
127
523
|
* Word's `insertOoxml` at range level: a flat OPC `pkg:package` (or a bare fragment). One
|
|
128
524
|
* incoming paragraph's runs go at the start or the end of the span, or in its place; anything
|
|
129
525
|
* else is inserted as blocks before or after the paragraph. Returns what was inserted, as
|
|
130
|
-
* `insertXml` does.
|
|
526
|
+
* `insertXml` does. Across paragraphs (check 54 case 35) one paragraph's runs replace the range as
|
|
527
|
+
* `insertText('Replace')` replaces it, where the paragraphs joined; the start is the first paragraph's,
|
|
528
|
+
* the end the last's; several blocks go before the first paragraph or after the last, and replacing a
|
|
529
|
+
* range across paragraphs with several blocks is refused, as unmeasured.
|
|
131
530
|
*/
|
|
132
531
|
async insertOoxml(ooxml, location) {
|
|
133
532
|
var _a, _b, _c;
|
|
533
|
+
this.assertWritableSpan();
|
|
134
534
|
const body = this.paragraph.parentBody;
|
|
135
535
|
const preprocess = (_a = body.package_) === null || _a === void 0 ? void 0 : _a.loadOptions.preprocessor;
|
|
136
536
|
const elements = await contentOf(ooxml, { preprocess: preprocess ? (doc) => preprocess(doc) : undefined, target: body.part });
|
|
@@ -139,31 +539,241 @@ export class Range {
|
|
|
139
539
|
const only = elements.length === 1 && typeNameOf(elements[0]) === 'org_docx4j_wml.P' ? elements[0].value : undefined;
|
|
140
540
|
if (only) {
|
|
141
541
|
if (location === 'Replace') {
|
|
142
|
-
this.
|
|
143
|
-
|
|
542
|
+
const landing = this.removeKeepingMark('insertOoxml');
|
|
543
|
+
landing.paragraph.insertItemsAt(landing.offset, ((_b = only.content) !== null && _b !== void 0 ? _b : []));
|
|
544
|
+
return [landing.paragraph];
|
|
144
545
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
return [
|
|
546
|
+
const [paragraph, at] = location === 'Before' ? [this.paragraph, this.start] : [this.endParagraph, this.end];
|
|
547
|
+
paragraph.insertItemsAt(at, ((_c = only.content) !== null && _c !== void 0 ? _c : []));
|
|
548
|
+
return [paragraph];
|
|
148
549
|
}
|
|
149
|
-
|
|
550
|
+
if (location === 'Replace' && this.spansParagraphs) {
|
|
551
|
+
throw new NotSupportedError('Range', 'insertOoxml', "several blocks in place of a range across paragraphs, which Word wasn't measured doing (CR-014)");
|
|
552
|
+
}
|
|
553
|
+
const target = location === 'Before' ? this.paragraph : this.endParagraph;
|
|
554
|
+
body.insertElement(elements, location === 'Before' ? 'Before' : 'After', target);
|
|
150
555
|
if (location === 'Replace')
|
|
151
|
-
this.
|
|
556
|
+
this.removeKeepingMark('insertOoxml');
|
|
152
557
|
return elements.map((el) => {
|
|
153
558
|
var _a;
|
|
154
559
|
return typeNameOf(el) === 'org_docx4j_wml.P'
|
|
155
|
-
? (_a = body.paragraphFor(el.value)) !== null && _a !== void 0 ? _a : { element: el, container:
|
|
156
|
-
: { element: el, container:
|
|
560
|
+
? (_a = body.paragraphFor(el.value)) !== null && _a !== void 0 ? _a : { element: el, container: target.container }
|
|
561
|
+
: { element: el, container: target.container };
|
|
157
562
|
});
|
|
158
563
|
}
|
|
159
|
-
/**
|
|
564
|
+
/**
|
|
565
|
+
* The range's content removed as a replacement removes it, keeping a last mark the range holds (check 52):
|
|
566
|
+
* `delete()`'s join across paragraphs, else the paragraph's splice. Where the paragraphs closed up.
|
|
567
|
+
*/
|
|
568
|
+
removeKeepingMark(member) {
|
|
569
|
+
if (!this.spansParagraphs && !this.holdsEndMark) {
|
|
570
|
+
this.paragraph.splice(this.start, this.end, '', 'before', this.edges);
|
|
571
|
+
this.end = this.start;
|
|
572
|
+
return { paragraph: this.paragraph, offset: this.start };
|
|
573
|
+
}
|
|
574
|
+
const extent = this.replaceableExtent(member);
|
|
575
|
+
if (this.paragraph.changeTracker)
|
|
576
|
+
assertTrackable(extent, member);
|
|
577
|
+
const landing = deleteSpan(this, extent, true);
|
|
578
|
+
this.landAt(landing.paragraph, landing.offset);
|
|
579
|
+
return landing;
|
|
580
|
+
}
|
|
581
|
+
/**
|
|
582
|
+
* The extent of a range a replacement may take: refused where it ends with a whole table, as Word refused it
|
|
583
|
+
* (`GeneralException`, check 54 case 7), and where it starts with a whole table or control (unmeasured).
|
|
584
|
+
*/
|
|
585
|
+
replaceableExtent(member) {
|
|
586
|
+
const extent = extentOf(this, member);
|
|
587
|
+
if (!extent.tail) {
|
|
588
|
+
throw new GeneralException(`Range.${member}: this range ends with a whole table or control, which Office JS refuses to replace (check 54)`);
|
|
589
|
+
}
|
|
590
|
+
if (!extent.head) {
|
|
591
|
+
throw new NotSupportedError('Range', member, "this range starts with a whole table or content control, so there is no paragraph for the text to go in, and what Word does isn't measured");
|
|
592
|
+
}
|
|
593
|
+
this.assertWritableSpan();
|
|
594
|
+
return extent;
|
|
595
|
+
}
|
|
596
|
+
/**
|
|
597
|
+
* Removes the span's text, and the pictures in it (CR-002 section 48). Across paragraphs, or holding its
|
|
598
|
+
* paragraph's mark, it is Word's (CR-014 phase 2, check 52; `deleteSpan`): the paragraphs join in the last one's
|
|
599
|
+
* properties, the blocks between go, a last mark the range holds goes with it, joining the paragraph after; a
|
|
600
|
+
* footnote whose reference goes goes on save. Tracked, the text is deleted and every mark in the range marked
|
|
601
|
+
* deleted. A range starting or ending inside a table is refused with `GeneralException`, as Office JS refuses it.
|
|
602
|
+
*/
|
|
160
603
|
delete() {
|
|
604
|
+
// a control's own range takes a control showing its placeholder as Word does; a paragraph's 'Content' range over a
|
|
605
|
+
// block control's one paragraph takes the control, as Word's took one showing its placeholder (CR-002 section 63, D3)
|
|
606
|
+
if (this.ofControl) {
|
|
607
|
+
const { control, part } = this.ofControl;
|
|
608
|
+
if (control.deleteThrough(part)) {
|
|
609
|
+
this.end = this.start;
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
const emptied = part === 'Content' && !this.paragraph.changeTracker ? control : undefined;
|
|
613
|
+
this.deleteSpan();
|
|
614
|
+
// a control emptied shows its entry again in Word's form (check 66 case 3), Word's filler in the bare form (D10)
|
|
615
|
+
emptied === null || emptied === void 0 ? void 0 : emptied.showEmptied();
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
const control = this.whole && !this.mark && !this.spansParagraphs ? this.paragraph.blockControl : undefined;
|
|
619
|
+
if ((control === null || control === void 0 ? void 0 : control.isShowingPlaceholder) && control.paragraphs.length === 1 && this.start === 0 && this.end >= this.paragraph.text.length) {
|
|
620
|
+
const r = control.takeWhole();
|
|
621
|
+
this.landAt(r.paragraph, 0);
|
|
622
|
+
return;
|
|
623
|
+
}
|
|
624
|
+
this.deleteSpan();
|
|
625
|
+
}
|
|
626
|
+
/** `delete()`'s own work. */
|
|
627
|
+
deleteSpan() {
|
|
628
|
+
if (this.spansParagraphs || this.holdsEndMark) {
|
|
629
|
+
const extent = extentOf(this, 'delete', true);
|
|
630
|
+
this.assertWritableSpan();
|
|
631
|
+
if (this.paragraph.changeTracker)
|
|
632
|
+
assertTrackable(extent, 'delete');
|
|
633
|
+
const landing = deleteSpan(this, extent, false);
|
|
634
|
+
if (landing)
|
|
635
|
+
this.landAt(landing.paragraph, landing.offset);
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
161
638
|
this.paragraph.splice(this.start, this.end, '', 'before', this.edges);
|
|
162
639
|
this.end = this.start;
|
|
163
640
|
}
|
|
641
|
+
/** `insertText(text, 'Replace')` across paragraphs, and any under tracking (CR-014 phases 2 and 3). */
|
|
642
|
+
replaceAcross(text) {
|
|
643
|
+
const extent = this.replaceableExtent('insertText');
|
|
644
|
+
if (this.paragraph.changeTracker) {
|
|
645
|
+
assertTrackable(extent, 'insertText');
|
|
646
|
+
// tracked: the insertion before the deletion (check 52 cases 39 to 48), in the first replaced run's formatting
|
|
647
|
+
const length = this.spansParagraphs ? undefined : this.end - this.start;
|
|
648
|
+
const inserted = this.paragraph.splice(this.start, this.start, text, 'after');
|
|
649
|
+
// the range's own text follows what was put in, in the paragraph the insertion ended in (E1)
|
|
650
|
+
this.startParagraph = inserted.endParagraph;
|
|
651
|
+
this.start = inserted.end;
|
|
652
|
+
if (length !== undefined) {
|
|
653
|
+
this.lastParagraph = inserted.endParagraph;
|
|
654
|
+
this.end = inserted.end + length;
|
|
655
|
+
}
|
|
656
|
+
deleteSpan(this, extentOf(this, 'insertText'), true);
|
|
657
|
+
this.cover(inserted, true);
|
|
658
|
+
return new Range(inserted.paragraph, inserted.start, inserted.end, false, false, inserted.endParagraph);
|
|
659
|
+
}
|
|
660
|
+
const first = firstCharacterOf(this);
|
|
661
|
+
// a line break in the text is a paragraph break (E1), refused before anything changes where none can be made
|
|
662
|
+
if (/[\r\n]/.test(text))
|
|
663
|
+
this.paragraph.assertBreakableAt(this.start, 'before');
|
|
664
|
+
const landing = deleteSpan(this, extent, true);
|
|
665
|
+
const placed = placeReplacement(landing, first, text);
|
|
666
|
+
const r = new Range(placed.first, placed.start, placed.end, false, false, placed.last);
|
|
667
|
+
this.cover(r, true);
|
|
668
|
+
return r;
|
|
669
|
+
}
|
|
670
|
+
/**
|
|
671
|
+
* The hits of a query in the range, as Office JS's `search` gives them. A plain query holding `^p`, Word's
|
|
672
|
+
* paragraph mark, matches across paragraphs (`searchMarks`); any other matches a paragraph at a time.
|
|
673
|
+
*/
|
|
164
674
|
search(text, options) {
|
|
165
|
-
const
|
|
166
|
-
|
|
675
|
+
const marks = (options === null || options === void 0 ? void 0 : options.matchWildcards) || (options === null || options === void 0 ? void 0 : options.matchRegExp) ? undefined : paragraphMarkPieces(text);
|
|
676
|
+
if (marks)
|
|
677
|
+
return this.searchMarks(marks, options);
|
|
678
|
+
if ((options === null || options === void 0 ? void 0 : options.matchWildcards) && (this.spansParagraphs || this.holdsEndMark))
|
|
679
|
+
return this.searchWildcards(text, options);
|
|
680
|
+
if (this.spansParagraphs)
|
|
681
|
+
return this.pieces().flatMap((piece) => piece.search(text, options));
|
|
682
|
+
return officeMatches(this.paragraph.segments({ objects: true }), text, options, this.start, this.end)
|
|
683
|
+
.map(({ start, end }) => new Range(this.paragraph, start, end));
|
|
684
|
+
}
|
|
685
|
+
/**
|
|
686
|
+
* A query's hits where it holds `^p`, Word's paragraph mark, as Word finds them (check 52 case 2; CR-014 phase
|
|
687
|
+
* 3): `pieces` is the query split at its marks. A hit starts in one paragraph with the first piece matching to
|
|
688
|
+
* its end, takes each middle piece as a whole paragraph's text, and ends with the last piece matching from the
|
|
689
|
+
* next paragraph's start; a query ending with `^p` ends with that paragraph's mark, which the hit holds. A mark
|
|
690
|
+
* matches only a paragraph's own mark the range holds: never a cell's or a row's end, and never across the edge
|
|
691
|
+
* of a table, so every paragraph of a hit is in the same cell or in none (`^p` between a list item and the first
|
|
692
|
+
* cell after it found nothing). `^p^p` matches an empty paragraph's mark after another mark. Plain queries only:
|
|
693
|
+
* Word's wildcards write the mark another way.
|
|
694
|
+
*/
|
|
695
|
+
searchMarks(pieces, options) {
|
|
696
|
+
const paragraphs = this.paragraphs;
|
|
697
|
+
const last = paragraphs.length - 1;
|
|
698
|
+
const marks = pieces.length - 1;
|
|
699
|
+
const endsWithMark = pieces[marks] === '';
|
|
700
|
+
const cell = (p) => { var _a; return (_a = p.parentTableCell) === null || _a === void 0 ? void 0 : _a.element.value; };
|
|
701
|
+
// a paragraph's mark the search may match: the range holds it, and it is a paragraph's, not a cell's end
|
|
702
|
+
const markIn = (j) => (j < last || this.holdsEndMark) && paragraphs[j].markText === '\r';
|
|
703
|
+
const matches = (p, query, from, to) => officeMatches(p.segments({ objects: true }), query, options, from, to);
|
|
704
|
+
const out = [];
|
|
705
|
+
for (let i = 0; i <= last; i++) {
|
|
706
|
+
const end = i + marks - (endsWithMark ? 1 : 0); // the paragraph the hit ends in
|
|
707
|
+
if (end > last)
|
|
708
|
+
break;
|
|
709
|
+
const scope = cell(paragraphs[i]);
|
|
710
|
+
let ok = true;
|
|
711
|
+
for (let j = i; j < i + marks && ok; j++)
|
|
712
|
+
ok = markIn(j);
|
|
713
|
+
for (let j = i + 1; j <= end && ok; j++)
|
|
714
|
+
ok = cell(paragraphs[j]) === scope;
|
|
715
|
+
// the middle pieces: each a whole paragraph's text (an empty one for an empty piece)
|
|
716
|
+
for (let j = 1; j < marks && ok; j++) {
|
|
717
|
+
const p = paragraphs[i + j];
|
|
718
|
+
ok = pieces[j] === '' ? p.text === '' : matches(p, pieces[j], 0, p.text.length).some((m) => m.start === 0 && m.end === p.text.length);
|
|
719
|
+
}
|
|
720
|
+
if (!ok)
|
|
721
|
+
continue;
|
|
722
|
+
const first = paragraphs[i];
|
|
723
|
+
const from = i === 0 ? this.start : 0;
|
|
724
|
+
const starts = pieces[0] === '' ? [first.text.length]
|
|
725
|
+
: matches(first, pieces[0], from, first.text.length).filter((m) => m.end === first.text.length).map((m) => m.start);
|
|
726
|
+
if (starts.length === 0 || starts[0] < from)
|
|
727
|
+
continue;
|
|
728
|
+
const endParagraph = paragraphs[end];
|
|
729
|
+
let to;
|
|
730
|
+
if (endsWithMark)
|
|
731
|
+
to = endParagraph.text.length;
|
|
732
|
+
else {
|
|
733
|
+
const limit = end === last ? this.end : endParagraph.text.length;
|
|
734
|
+
const hit = matches(endParagraph, pieces[marks], 0, limit).find((m) => m.start === 0);
|
|
735
|
+
if (!hit)
|
|
736
|
+
continue;
|
|
737
|
+
to = hit.end;
|
|
738
|
+
}
|
|
739
|
+
out.push(new Range(first, starts[0], to, false, endsWithMark, endParagraph.element === first.element ? undefined : endParagraph));
|
|
740
|
+
}
|
|
741
|
+
return out;
|
|
742
|
+
}
|
|
743
|
+
/**
|
|
744
|
+
* A wildcard query's hits across the paragraphs' marks, as Word's wildcards match them (check 59; CR-002 section 32,
|
|
745
|
+
* K7): where `^p` matches one (`searchMarks`), a paragraph's own mark the range holds, never a cell's or a row's end,
|
|
746
|
+
* and never across a table's edge, so the paragraphs a hit spans are in one cell or in none. `?`, `*` and a class cross
|
|
747
|
+
* such a mark and `^13` is one; a hit ending with one holds it.
|
|
748
|
+
*/
|
|
749
|
+
searchWildcards(query, options) {
|
|
750
|
+
const paragraphs = this.paragraphs;
|
|
751
|
+
const last = paragraphs.length - 1;
|
|
752
|
+
const cell = (p) => { var _a; return (_a = p.parentTableCell) === null || _a === void 0 ? void 0 : _a.element.value; };
|
|
753
|
+
const markIn = (j) => (j < last || this.holdsEndMark) && paragraphs[j].markText === '\r';
|
|
754
|
+
// runs of consecutive paragraphs in one cell, or in none
|
|
755
|
+
const groups = [];
|
|
756
|
+
paragraphs.forEach((p, j) => {
|
|
757
|
+
const group = groups[groups.length - 1];
|
|
758
|
+
if (group && cell(paragraphs[group[group.length - 1]]) === cell(p))
|
|
759
|
+
group.push(j);
|
|
760
|
+
else
|
|
761
|
+
groups.push([j]);
|
|
762
|
+
});
|
|
763
|
+
const out = [];
|
|
764
|
+
for (const group of groups) {
|
|
765
|
+
const pieces = group.map((j) => {
|
|
766
|
+
const p = paragraphs[j];
|
|
767
|
+
const length = p.text.length;
|
|
768
|
+
return { segments: p.segments({ objects: true }), from: j === 0 ? this.start : 0, to: j === last ? this.end : length, length, mark: markIn(j) };
|
|
769
|
+
});
|
|
770
|
+
for (const m of wildcardMatchesAcross(pieces, query, options)) {
|
|
771
|
+
const first = paragraphs[group[m.first]];
|
|
772
|
+
const end = paragraphs[group[m.last]];
|
|
773
|
+
out.push(new Range(first, m.start, m.end, false, m.mark, end.element === first.element ? undefined : end));
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
return out;
|
|
167
777
|
}
|
|
168
778
|
/**
|
|
169
779
|
* Replaces every match in this span, last first so the offsets stay valid; returns the count
|
|
@@ -171,30 +781,94 @@ export class Range {
|
|
|
171
781
|
* (section 26; this one was missed there, section 31).
|
|
172
782
|
*/
|
|
173
783
|
replaceText(find, replace, options) {
|
|
174
|
-
|
|
175
|
-
|
|
784
|
+
refuseMarkReplacement('Range', find, options);
|
|
785
|
+
if (options === null || options === void 0 ? void 0 : options.matchWildcards)
|
|
786
|
+
refuseWildcardMarkReplacement('Range', this.search(find, options));
|
|
787
|
+
// across paragraphs a paragraph at a time, as `search` (CR-014 section 3), the last first
|
|
788
|
+
if (this.spansParagraphs) {
|
|
789
|
+
this.assertWritableSpan();
|
|
790
|
+
return this.pieces().reverse().reduce((count, piece) => count + piece.replaceText(find, replace, options), 0);
|
|
791
|
+
}
|
|
792
|
+
const matches = officeMatches(this.paragraph.segments({ objects: true }), find, options, this.start, this.end);
|
|
176
793
|
for (let i = matches.length - 1; i >= 0; i--) {
|
|
177
|
-
const match = matches[i];
|
|
178
|
-
new Range(this.paragraph,
|
|
179
|
-
.insertText(expandReplacement(replace, match, options), 'Replace');
|
|
794
|
+
const { start, end, match } = matches[i];
|
|
795
|
+
new Range(this.paragraph, start, end).insertText(expandReplacement(replace, match, options), 'Replace');
|
|
180
796
|
}
|
|
181
797
|
return matches.length;
|
|
182
798
|
}
|
|
183
799
|
/** The tracked changes this span covers, in document order (CR-002 phase F). */
|
|
184
800
|
getTrackedChanges() {
|
|
801
|
+
if (this.spansParagraphs) {
|
|
802
|
+
// the story's listing, whose changes run across paragraphs as Word's do (check 52: a deletion through a
|
|
803
|
+
// mark is one change), each that overlaps the range; a paragraph's own listing cuts a change at its edges
|
|
804
|
+
const { layout, first, last } = this.located();
|
|
805
|
+
const at = (p, offset) => {
|
|
806
|
+
const i = layout.indexOf(p.p);
|
|
807
|
+
return i < 0 ? Number.NaN : layout.paragraphs[i].start + offset;
|
|
808
|
+
};
|
|
809
|
+
const from = layout.paragraphs[first].start + this.start;
|
|
810
|
+
const to = layout.paragraphs[last].start + this.end;
|
|
811
|
+
return this.storyBody().getTrackedChanges().filter((change) => {
|
|
812
|
+
const range = change.getRange();
|
|
813
|
+
if (!range)
|
|
814
|
+
return false;
|
|
815
|
+
const start = at(range.paragraph, range.start);
|
|
816
|
+
const end = at(range.endParagraph, range.end);
|
|
817
|
+
return !Number.isNaN(start) && !Number.isNaN(end) && start <= to && end >= from;
|
|
818
|
+
});
|
|
819
|
+
}
|
|
185
820
|
return this.paragraph.getTrackedChanges().filter((change) => {
|
|
186
821
|
const range = change.getRange();
|
|
187
822
|
if (!range)
|
|
188
823
|
return false;
|
|
189
|
-
|
|
824
|
+
// a change across paragraphs (CR-014) covers this paragraph from where it starts in it to where it ends in it
|
|
825
|
+
const start = range.paragraph.element === this.paragraph.element ? range.start : 0;
|
|
826
|
+
const end = range.endParagraph.element === this.paragraph.element ? range.end : Number.POSITIVE_INFINITY;
|
|
827
|
+
return start <= this.end && end >= this.start;
|
|
190
828
|
});
|
|
191
829
|
}
|
|
830
|
+
/**
|
|
831
|
+
* A range of this one, as Office JS's `getRange` (check 52): 'Whole' is the range itself, 'Content' the same
|
|
832
|
+
* without a last mark, 'Start' and 'End' collapsed at its ends, and 'After' collapsed just after it - at the
|
|
833
|
+
* start of the next paragraph where the range holds its last paragraph's mark.
|
|
834
|
+
*/
|
|
192
835
|
getRange(location = 'Whole') {
|
|
193
836
|
if (location === 'Start')
|
|
194
837
|
return new Range(this.paragraph, this.start, this.start);
|
|
195
838
|
if (location === 'End')
|
|
196
|
-
return new Range(this.
|
|
197
|
-
|
|
839
|
+
return new Range(this.endParagraph, this.end, this.end);
|
|
840
|
+
if (location === 'After') {
|
|
841
|
+
if (this.mark && this.end >= this.endParagraph.text.length) {
|
|
842
|
+
const layout = storyLayout(storyOf(this.endParagraph.p).items, (element, container) => this.viewOf(element, container).text);
|
|
843
|
+
const next = layout.paragraphs[layout.indexOf(this.endParagraph.p) + 1];
|
|
844
|
+
if (next)
|
|
845
|
+
return new Range(this.viewOf(next.element, next.container), 0, 0);
|
|
846
|
+
}
|
|
847
|
+
return new Range(this.endParagraph, this.end, this.end);
|
|
848
|
+
}
|
|
849
|
+
return new Range(this.paragraph, this.start, this.end, this.whole, this.mark && location === 'Whole', this.spansParagraphs ? this.endParagraph : undefined, this.endMark);
|
|
850
|
+
}
|
|
851
|
+
/**
|
|
852
|
+
* A range from this range's start or `range`'s, whichever comes first, to the end that comes last (Office JS
|
|
853
|
+
* `expandTo`; which is first doesn't matter, check 52). Both must be in one story.
|
|
854
|
+
*/
|
|
855
|
+
expandTo(range) {
|
|
856
|
+
const story = storyOf(this.paragraph.p).items;
|
|
857
|
+
// Office JS's ItemNotFound (check 53: a body range expandTo a footnote's)
|
|
858
|
+
if (storyOf(range.paragraph.p).items !== story)
|
|
859
|
+
throw new ItemNotFound('expandTo: the two ranges are in different stories');
|
|
860
|
+
const layout = storyLayout(story, (element, container) => this.viewOf(element, container).text);
|
|
861
|
+
const at = (p, offset) => {
|
|
862
|
+
const i = layout.indexOf(p.p);
|
|
863
|
+
if (i < 0)
|
|
864
|
+
throw new Docx4JException('expandTo: a range is outside the story as a reader reads it');
|
|
865
|
+
return layout.paragraphs[i].start + offset;
|
|
866
|
+
};
|
|
867
|
+
const first = at(range.paragraph, range.start) < at(this.paragraph, this.start) ? range : this;
|
|
868
|
+
const thisEnd = at(this.endParagraph, this.end) + (this.mark ? 1 : 0);
|
|
869
|
+
const rangeEnd = at(range.endParagraph, range.end) + (range.mark ? 1 : 0);
|
|
870
|
+
const last = rangeEnd > thisEnd ? range : this;
|
|
871
|
+
return new Range(first.paragraph, first.start, last.end, first.whole && last.whole, last.mark, last.endParagraph.element === first.paragraph.element ? undefined : last.endParagraph, last.endMark);
|
|
198
872
|
}
|
|
199
873
|
/** The span's text as it stands (extension; Office JS getOoxml wraps it in a package). */
|
|
200
874
|
toString() {
|
|
@@ -203,7 +877,18 @@ export class Range {
|
|
|
203
877
|
// --- comments (CR-002 phase G) ---
|
|
204
878
|
/** The comments this span touches, replies nested under their parent. */
|
|
205
879
|
async getComments() {
|
|
206
|
-
|
|
880
|
+
if (!this.spansParagraphs)
|
|
881
|
+
return commentApi().commentsOf(this);
|
|
882
|
+
const seen = new Set();
|
|
883
|
+
const out = [];
|
|
884
|
+
for (const piece of this.pieces()) {
|
|
885
|
+
for (const comment of await commentApi().commentsOf(piece))
|
|
886
|
+
if (!seen.has(comment.id)) {
|
|
887
|
+
seen.add(comment.id);
|
|
888
|
+
out.push(comment);
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
return out;
|
|
207
892
|
}
|
|
208
893
|
/**
|
|
209
894
|
* Comments the span: the markers and the reference run around it (runs are split at the
|
|
@@ -211,6 +896,8 @@ export class Range {
|
|
|
211
896
|
* the comment parts the document lacks. Asynchronous because it unmarshals those parts.
|
|
212
897
|
*/
|
|
213
898
|
async insertComment(content, options) {
|
|
899
|
+
// across paragraphs (CR-014 phase 3) the markers go in the first and the last paragraph, as Word's (check 52)
|
|
900
|
+
this.assertWritableSpan();
|
|
214
901
|
return commentApi().insertComment(this, content, options);
|
|
215
902
|
}
|
|
216
903
|
// --- content controls (CR-002 phase E) ---
|
|
@@ -221,6 +908,9 @@ export class Range {
|
|
|
221
908
|
*/
|
|
222
909
|
insertContentControl(kind) {
|
|
223
910
|
var _a;
|
|
911
|
+
if (this.spansParagraphs)
|
|
912
|
+
return this.wrapParagraphs(kind);
|
|
913
|
+
this.paragraph.assertWritable();
|
|
224
914
|
const paragraph = this.paragraph;
|
|
225
915
|
const body = paragraph.parentBody;
|
|
226
916
|
// The control is built empty first: `sdt` refuses a kind that cannot be run-level (a repeating
|
|
@@ -229,7 +919,14 @@ export class Range {
|
|
|
229
919
|
declareSdtExtensions(body, sdt);
|
|
230
920
|
if (this.start === this.end) {
|
|
231
921
|
paragraph.insertItemsAt(this.start, [sdt]);
|
|
232
|
-
|
|
922
|
+
const control = new ContentControl(sdt, containerOf(sdt, paragraph), body);
|
|
923
|
+
// a control showing Word's default placeholder, as Word's made at a point (CR-002 section 63, D8; check 64); a
|
|
924
|
+
// picture control Word's form, the flag and the placeholder picture (CR-005 section 10, D2)
|
|
925
|
+
if (showsPlaceholderWhenMade(kind))
|
|
926
|
+
control.showPlaceholder();
|
|
927
|
+
else if (kind === 'Picture')
|
|
928
|
+
showPicturePlaceholder(control);
|
|
929
|
+
return control;
|
|
233
930
|
}
|
|
234
931
|
// Every refusal before anything changes (CR-002 section 43): until then the runs were split first.
|
|
235
932
|
const covered = coveredSegments(paragraph, this.start, this.end);
|
|
@@ -274,6 +971,34 @@ export class Range {
|
|
|
274
971
|
markHolderInserted(tracker, sdt, owner, () => { });
|
|
275
972
|
return new ContentControl(sdt, owner, body);
|
|
276
973
|
}
|
|
974
|
+
/**
|
|
975
|
+
* A block content control around the paragraphs of a range across paragraphs, as Word puts one (check 54 cases
|
|
976
|
+
* 38 and 43, untracked and tracked): only a range holding its paragraphs whole, from the first's start to the
|
|
977
|
+
* last's end, its mark or not. Any other, starting or ending inside a paragraph or a table, is refused with
|
|
978
|
+
* `GeneralException`, as Word refused it (cases 36, 37 and 39 to 42). Tracked, the control is an insertion, as a
|
|
979
|
+
* paragraph's control is (check 30's markers).
|
|
980
|
+
*/
|
|
981
|
+
wrapParagraphs(kind) {
|
|
982
|
+
var _a;
|
|
983
|
+
if (this.start !== 0 || this.end < this.endParagraph.text.length) {
|
|
984
|
+
throw new GeneralException('Range.insertContentControl: a range across paragraphs takes a control only where it holds its paragraphs whole, as Office JS (check 54)');
|
|
985
|
+
}
|
|
986
|
+
const extent = extentOf(this, 'insertContentControl');
|
|
987
|
+
this.assertWritableSpan();
|
|
988
|
+
const body = this.paragraph.parentBody;
|
|
989
|
+
const { list, first, last } = extent;
|
|
990
|
+
const parent = (_a = list[first].value.PARENT) !== null && _a !== void 0 ? _a : body.container;
|
|
991
|
+
// a bound block control holds one paragraph (CR-002 section 61): checked before sdtOf, which takes the paragraphs
|
|
992
|
+
assertControlHolds(body, parent, list.filter((_, i) => i < first || i > last), [BLOCK_CONTROL], undefined, ['Range', 'insertContentControl']);
|
|
993
|
+
const sdt = sdtOf(list.slice(first, last + 1), { kind: sdtKindFor(kind), id: nextSdtId(controlIdScope(body)), form: 'block' });
|
|
994
|
+
list.splice(first, last - first + 1, sdt);
|
|
995
|
+
linkParents(sdt, parent);
|
|
996
|
+
declareSdtExtensions(body, sdt);
|
|
997
|
+
const tracker = this.paragraph.changeTracker;
|
|
998
|
+
if (tracker)
|
|
999
|
+
markHolderInserted(tracker, sdt, list, () => { });
|
|
1000
|
+
return new ContentControl(sdt, list, body);
|
|
1001
|
+
}
|
|
277
1002
|
/**
|
|
278
1003
|
* Office JS `Range.hyperlink`: the address of the first hyperlink in this range, or a new
|
|
279
1004
|
* hyperlink over it. `address#location` separates the address from an optional location within
|
|
@@ -299,16 +1024,20 @@ export class Range {
|
|
|
299
1024
|
* `font` write is.
|
|
300
1025
|
*/
|
|
301
1026
|
get hyperlink() {
|
|
302
|
-
var _a;
|
|
1027
|
+
var _a, _b;
|
|
1028
|
+
if (this.spansParagraphs)
|
|
1029
|
+
return (_a = this.pieces().map((piece) => piece.hyperlink).find((link) => link !== '')) !== null && _a !== void 0 ? _a : '';
|
|
303
1030
|
const holder = this.hyperlinkHolder();
|
|
304
1031
|
if (holder === undefined)
|
|
305
1032
|
return '';
|
|
306
|
-
const anchor = (
|
|
1033
|
+
const anchor = (_b = holder.value.anchor) !== null && _b !== void 0 ? _b : '';
|
|
307
1034
|
const address = holder.value.id === undefined ? '' : this.relationshipTarget(holder.value.id);
|
|
308
1035
|
return anchor === '' ? address : `${address}#${anchor}`;
|
|
309
1036
|
}
|
|
310
1037
|
set hyperlink(value) {
|
|
311
1038
|
var _a, _b, _c;
|
|
1039
|
+
this.assertOneParagraph('hyperlink');
|
|
1040
|
+
this.paragraph.assertWritable();
|
|
312
1041
|
// Office JS refuses a hyperlink set on an empty range - inside a link, in plain text, just after a
|
|
313
1042
|
// link, and "" alike - with GeneralException, and changes nothing (test/README.md check 19, Word
|
|
314
1043
|
// 16.0.20326.20158; CR-002 section 25). Before, the setter removed the link under a caret and
|
|
@@ -519,15 +1248,12 @@ export class Range {
|
|
|
519
1248
|
return part.getRelationshipsPart(true).addExternalRelationship(Namespaces.HYPERLINK, address).id;
|
|
520
1249
|
}
|
|
521
1250
|
}
|
|
522
|
-
/**
|
|
523
|
-
|
|
524
|
-
* add-in code branching on `error.code` behaves the same here (check 19).
|
|
525
|
-
*/
|
|
526
|
-
class GeneralException extends Docx4JException {
|
|
1251
|
+
/** What Office JS throws where nothing matches (`ItemNotFound`): its `code`, as `GeneralException` carries its own. */
|
|
1252
|
+
class ItemNotFound extends Docx4JException {
|
|
527
1253
|
constructor(message) {
|
|
528
1254
|
super(message);
|
|
529
|
-
this.code = '
|
|
530
|
-
this.name = '
|
|
1255
|
+
this.code = 'ItemNotFound';
|
|
1256
|
+
this.name = 'ItemNotFound';
|
|
531
1257
|
}
|
|
532
1258
|
}
|
|
533
1259
|
/** The character style Word gives the runs of a hyperlink it makes. */
|