docxmlater 11.0.10 → 12.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (354) hide show
  1. package/README.md +51 -13
  2. package/dist/core/Document.d.ts +28 -2
  3. package/dist/core/Document.d.ts.map +1 -1
  4. package/dist/core/Document.js +725 -170
  5. package/dist/core/Document.js.map +1 -1
  6. package/dist/core/DocumentGenerator.d.ts +1 -0
  7. package/dist/core/DocumentGenerator.d.ts.map +1 -1
  8. package/dist/core/DocumentGenerator.js +77 -25
  9. package/dist/core/DocumentGenerator.js.map +1 -1
  10. package/dist/core/DocumentParser.d.ts +7 -3
  11. package/dist/core/DocumentParser.d.ts.map +1 -1
  12. package/dist/core/DocumentParser.js +427 -81
  13. package/dist/core/DocumentParser.js.map +1 -1
  14. package/dist/core/RelationshipManager.d.ts.map +1 -1
  15. package/dist/core/RelationshipManager.js +2 -2
  16. package/dist/core/RelationshipManager.js.map +1 -1
  17. package/dist/elements/Comment.d.ts +15 -1
  18. package/dist/elements/Comment.d.ts.map +1 -1
  19. package/dist/elements/Comment.js +67 -12
  20. package/dist/elements/Comment.js.map +1 -1
  21. package/dist/elements/CommentManager.d.ts +4 -0
  22. package/dist/elements/CommentManager.d.ts.map +1 -1
  23. package/dist/elements/CommentManager.js +74 -16
  24. package/dist/elements/CommentManager.js.map +1 -1
  25. package/dist/elements/EndnoteManager.d.ts.map +1 -1
  26. package/dist/elements/EndnoteManager.js.map +1 -1
  27. package/dist/elements/Field.d.ts +10 -1
  28. package/dist/elements/Field.d.ts.map +1 -1
  29. package/dist/elements/Field.js +76 -34
  30. package/dist/elements/Field.js.map +1 -1
  31. package/dist/elements/FieldHelpers.d.ts +1 -0
  32. package/dist/elements/FieldHelpers.d.ts.map +1 -1
  33. package/dist/elements/FieldHelpers.js +8 -4
  34. package/dist/elements/FieldHelpers.js.map +1 -1
  35. package/dist/elements/Footer.d.ts +3 -1
  36. package/dist/elements/Footer.d.ts.map +1 -1
  37. package/dist/elements/Footer.js +39 -53
  38. package/dist/elements/Footer.js.map +1 -1
  39. package/dist/elements/FootnoteManager.d.ts.map +1 -1
  40. package/dist/elements/FootnoteManager.js.map +1 -1
  41. package/dist/elements/Header.d.ts +3 -1
  42. package/dist/elements/Header.d.ts.map +1 -1
  43. package/dist/elements/Header.js +39 -53
  44. package/dist/elements/Header.js.map +1 -1
  45. package/dist/elements/HeaderFooterManager.d.ts +4 -2
  46. package/dist/elements/HeaderFooterManager.d.ts.map +1 -1
  47. package/dist/elements/HeaderFooterManager.js +36 -6
  48. package/dist/elements/HeaderFooterManager.js.map +1 -1
  49. package/dist/elements/Hyperlink.d.ts +6 -1
  50. package/dist/elements/Hyperlink.d.ts.map +1 -1
  51. package/dist/elements/Hyperlink.js +69 -44
  52. package/dist/elements/Hyperlink.js.map +1 -1
  53. package/dist/elements/Image.d.ts.map +1 -1
  54. package/dist/elements/Image.js +33 -6
  55. package/dist/elements/Image.js.map +1 -1
  56. package/dist/elements/ImageManager.d.ts +1 -0
  57. package/dist/elements/ImageManager.d.ts.map +1 -1
  58. package/dist/elements/ImageManager.js +11 -0
  59. package/dist/elements/ImageManager.js.map +1 -1
  60. package/dist/elements/Paragraph.d.ts +3 -0
  61. package/dist/elements/Paragraph.d.ts.map +1 -1
  62. package/dist/elements/Paragraph.js +91 -26
  63. package/dist/elements/Paragraph.js.map +1 -1
  64. package/dist/elements/Revision.d.ts +4 -0
  65. package/dist/elements/Revision.d.ts.map +1 -1
  66. package/dist/elements/Revision.js +369 -29
  67. package/dist/elements/Revision.js.map +1 -1
  68. package/dist/elements/Run.d.ts +1 -0
  69. package/dist/elements/Run.d.ts.map +1 -1
  70. package/dist/elements/Run.js +24 -4
  71. package/dist/elements/Run.js.map +1 -1
  72. package/dist/elements/Section.d.ts.map +1 -1
  73. package/dist/elements/Section.js +17 -7
  74. package/dist/elements/Section.js.map +1 -1
  75. package/dist/elements/Shape.d.ts.map +1 -1
  76. package/dist/elements/Shape.js +50 -42
  77. package/dist/elements/Shape.js.map +1 -1
  78. package/dist/elements/StructuredDocumentTag.d.ts +9 -1
  79. package/dist/elements/StructuredDocumentTag.d.ts.map +1 -1
  80. package/dist/elements/StructuredDocumentTag.js +46 -6
  81. package/dist/elements/StructuredDocumentTag.js.map +1 -1
  82. package/dist/elements/Table.d.ts +2 -0
  83. package/dist/elements/Table.d.ts.map +1 -1
  84. package/dist/elements/Table.js +90 -42
  85. package/dist/elements/Table.js.map +1 -1
  86. package/dist/elements/TableCell.d.ts +1 -0
  87. package/dist/elements/TableCell.d.ts.map +1 -1
  88. package/dist/elements/TableCell.js +32 -13
  89. package/dist/elements/TableCell.js.map +1 -1
  90. package/dist/elements/TableOfContents.d.ts.map +1 -1
  91. package/dist/elements/TableOfContents.js +5 -7
  92. package/dist/elements/TableOfContents.js.map +1 -1
  93. package/dist/elements/TableRow.d.ts +1 -0
  94. package/dist/elements/TableRow.d.ts.map +1 -1
  95. package/dist/elements/TableRow.js +16 -1
  96. package/dist/elements/TableRow.js.map +1 -1
  97. package/dist/elements/TextBox.d.ts.map +1 -1
  98. package/dist/elements/TextBox.js +55 -42
  99. package/dist/elements/TextBox.js.map +1 -1
  100. package/dist/esm/core/Document.js +726 -171
  101. package/dist/esm/core/Document.js.map +1 -1
  102. package/dist/esm/core/DocumentGenerator.js +77 -25
  103. package/dist/esm/core/DocumentGenerator.js.map +1 -1
  104. package/dist/esm/core/DocumentParser.js +427 -81
  105. package/dist/esm/core/DocumentParser.js.map +1 -1
  106. package/dist/esm/core/RelationshipManager.js +2 -2
  107. package/dist/esm/core/RelationshipManager.js.map +1 -1
  108. package/dist/esm/elements/Comment.js +67 -12
  109. package/dist/esm/elements/Comment.js.map +1 -1
  110. package/dist/esm/elements/CommentManager.js +74 -16
  111. package/dist/esm/elements/CommentManager.js.map +1 -1
  112. package/dist/esm/elements/EndnoteManager.js.map +1 -1
  113. package/dist/esm/elements/Field.js +77 -35
  114. package/dist/esm/elements/Field.js.map +1 -1
  115. package/dist/esm/elements/FieldHelpers.js +7 -4
  116. package/dist/esm/elements/FieldHelpers.js.map +1 -1
  117. package/dist/esm/elements/Footer.js +39 -53
  118. package/dist/esm/elements/Footer.js.map +1 -1
  119. package/dist/esm/elements/FootnoteManager.js.map +1 -1
  120. package/dist/esm/elements/Header.js +39 -53
  121. package/dist/esm/elements/Header.js.map +1 -1
  122. package/dist/esm/elements/HeaderFooterManager.js +36 -6
  123. package/dist/esm/elements/HeaderFooterManager.js.map +1 -1
  124. package/dist/esm/elements/Hyperlink.js +69 -44
  125. package/dist/esm/elements/Hyperlink.js.map +1 -1
  126. package/dist/esm/elements/Image.js +33 -6
  127. package/dist/esm/elements/Image.js.map +1 -1
  128. package/dist/esm/elements/ImageManager.js +11 -0
  129. package/dist/esm/elements/ImageManager.js.map +1 -1
  130. package/dist/esm/elements/Paragraph.js +91 -26
  131. package/dist/esm/elements/Paragraph.js.map +1 -1
  132. package/dist/esm/elements/Revision.js +369 -29
  133. package/dist/esm/elements/Revision.js.map +1 -1
  134. package/dist/esm/elements/Run.js +24 -4
  135. package/dist/esm/elements/Run.js.map +1 -1
  136. package/dist/esm/elements/Section.js +17 -7
  137. package/dist/esm/elements/Section.js.map +1 -1
  138. package/dist/esm/elements/Shape.js +50 -42
  139. package/dist/esm/elements/Shape.js.map +1 -1
  140. package/dist/esm/elements/StructuredDocumentTag.js +46 -6
  141. package/dist/esm/elements/StructuredDocumentTag.js.map +1 -1
  142. package/dist/esm/elements/Table.js +90 -42
  143. package/dist/esm/elements/Table.js.map +1 -1
  144. package/dist/esm/elements/TableCell.js +32 -13
  145. package/dist/esm/elements/TableCell.js.map +1 -1
  146. package/dist/esm/elements/TableOfContents.js +5 -7
  147. package/dist/esm/elements/TableOfContents.js.map +1 -1
  148. package/dist/esm/elements/TableRow.js +16 -1
  149. package/dist/esm/elements/TableRow.js.map +1 -1
  150. package/dist/esm/elements/TextBox.js +55 -42
  151. package/dist/esm/elements/TextBox.js.map +1 -1
  152. package/dist/esm/formatting/AbstractNumbering.js +1 -1
  153. package/dist/esm/formatting/AbstractNumbering.js.map +1 -1
  154. package/dist/esm/formatting/NumberingLevel.js +8 -6
  155. package/dist/esm/formatting/NumberingLevel.js.map +1 -1
  156. package/dist/esm/formatting/NumberingManager.js +16 -1
  157. package/dist/esm/formatting/NumberingManager.js.map +1 -1
  158. package/dist/esm/formatting/Style.js +14 -0
  159. package/dist/esm/formatting/Style.js.map +1 -1
  160. package/dist/esm/formatting/StylesManager.js +14 -2
  161. package/dist/esm/formatting/StylesManager.js.map +1 -1
  162. package/dist/esm/helpers/CleanupHelper.js +103 -26
  163. package/dist/esm/helpers/CleanupHelper.js.map +1 -1
  164. package/dist/esm/images/ImageOptimizer.js +52 -4
  165. package/dist/esm/images/ImageOptimizer.js.map +1 -1
  166. package/dist/esm/index.js +1 -0
  167. package/dist/esm/index.js.map +1 -1
  168. package/dist/esm/internal.js +1 -1
  169. package/dist/esm/internal.js.map +1 -1
  170. package/dist/esm/processors/CompatibilityUpgrader.js +7 -1
  171. package/dist/esm/processors/CompatibilityUpgrader.js.map +1 -1
  172. package/dist/esm/processors/InMemoryRevisionAcceptor.js +134 -13
  173. package/dist/esm/processors/InMemoryRevisionAcceptor.js.map +1 -1
  174. package/dist/esm/processors/RevisionWalker.js +58 -8
  175. package/dist/esm/processors/RevisionWalker.js.map +1 -1
  176. package/dist/esm/processors/SelectiveRevisionAcceptor.js +116 -17
  177. package/dist/esm/processors/SelectiveRevisionAcceptor.js.map +1 -1
  178. package/dist/esm/processors/cnfStyleDecoder.js +7 -7
  179. package/dist/esm/processors/cnfStyleDecoder.js.map +1 -1
  180. package/dist/esm/processors/stripTrackedChanges.js +16 -5
  181. package/dist/esm/processors/stripTrackedChanges.js.map +1 -1
  182. package/dist/esm/utils/deepClone.js +24 -11
  183. package/dist/esm/utils/deepClone.js.map +1 -1
  184. package/dist/esm/utils/units.js +1 -1
  185. package/dist/esm/utils/units.js.map +1 -1
  186. package/dist/esm/utils/validation.js +11 -23
  187. package/dist/esm/utils/validation.js.map +1 -1
  188. package/dist/esm/utils/xmlSanitization.js +131 -7
  189. package/dist/esm/utils/xmlSanitization.js.map +1 -1
  190. package/dist/esm/validation/RevisionAutoFixer.js +69 -55
  191. package/dist/esm/validation/RevisionAutoFixer.js.map +1 -1
  192. package/dist/esm/validation/RevisionValidator.js +24 -20
  193. package/dist/esm/validation/RevisionValidator.js.map +1 -1
  194. package/dist/esm/xml/XMLBuilder.js +52 -20
  195. package/dist/esm/xml/XMLBuilder.js.map +1 -1
  196. package/dist/esm/xml/XMLParser.js +55 -13
  197. package/dist/esm/xml/XMLParser.js.map +1 -1
  198. package/dist/esm/zip/ZipHandler.js +37 -13
  199. package/dist/esm/zip/ZipHandler.js.map +1 -1
  200. package/dist/esm/zip/ZipReader.js +53 -6
  201. package/dist/esm/zip/ZipReader.js.map +1 -1
  202. package/dist/esm/zip/errors.js +7 -0
  203. package/dist/esm/zip/errors.js.map +1 -1
  204. package/dist/esm/zip/types.js +4 -0
  205. package/dist/esm/zip/types.js.map +1 -1
  206. package/dist/formatting/AbstractNumbering.d.ts.map +1 -1
  207. package/dist/formatting/AbstractNumbering.js +1 -1
  208. package/dist/formatting/AbstractNumbering.js.map +1 -1
  209. package/dist/formatting/NumberingLevel.d.ts +1 -1
  210. package/dist/formatting/NumberingLevel.d.ts.map +1 -1
  211. package/dist/formatting/NumberingLevel.js +8 -6
  212. package/dist/formatting/NumberingLevel.js.map +1 -1
  213. package/dist/formatting/NumberingManager.d.ts.map +1 -1
  214. package/dist/formatting/NumberingManager.js +16 -1
  215. package/dist/formatting/NumberingManager.js.map +1 -1
  216. package/dist/formatting/Style.d.ts.map +1 -1
  217. package/dist/formatting/Style.js +14 -0
  218. package/dist/formatting/Style.js.map +1 -1
  219. package/dist/formatting/StylesManager.d.ts +2 -0
  220. package/dist/formatting/StylesManager.d.ts.map +1 -1
  221. package/dist/formatting/StylesManager.js +14 -2
  222. package/dist/formatting/StylesManager.js.map +1 -1
  223. package/dist/helpers/CleanupHelper.d.ts.map +1 -1
  224. package/dist/helpers/CleanupHelper.js +103 -26
  225. package/dist/helpers/CleanupHelper.js.map +1 -1
  226. package/dist/images/ImageOptimizer.d.ts.map +1 -1
  227. package/dist/images/ImageOptimizer.js +52 -4
  228. package/dist/images/ImageOptimizer.js.map +1 -1
  229. package/dist/index.d.ts +3 -1
  230. package/dist/index.d.ts.map +1 -1
  231. package/dist/index.js +4 -2
  232. package/dist/index.js.map +1 -1
  233. package/dist/internal.d.ts +1 -1
  234. package/dist/internal.d.ts.map +1 -1
  235. package/dist/internal.js +2 -1
  236. package/dist/internal.js.map +1 -1
  237. package/dist/processors/CompatibilityUpgrader.d.ts.map +1 -1
  238. package/dist/processors/CompatibilityUpgrader.js +7 -1
  239. package/dist/processors/CompatibilityUpgrader.js.map +1 -1
  240. package/dist/processors/InMemoryRevisionAcceptor.d.ts +1 -0
  241. package/dist/processors/InMemoryRevisionAcceptor.d.ts.map +1 -1
  242. package/dist/processors/InMemoryRevisionAcceptor.js +135 -13
  243. package/dist/processors/InMemoryRevisionAcceptor.js.map +1 -1
  244. package/dist/processors/RevisionWalker.d.ts +2 -0
  245. package/dist/processors/RevisionWalker.d.ts.map +1 -1
  246. package/dist/processors/RevisionWalker.js +58 -8
  247. package/dist/processors/RevisionWalker.js.map +1 -1
  248. package/dist/processors/SelectiveRevisionAcceptor.d.ts.map +1 -1
  249. package/dist/processors/SelectiveRevisionAcceptor.js +116 -17
  250. package/dist/processors/SelectiveRevisionAcceptor.js.map +1 -1
  251. package/dist/processors/cnfStyleDecoder.d.ts.map +1 -1
  252. package/dist/processors/cnfStyleDecoder.js +7 -7
  253. package/dist/processors/cnfStyleDecoder.js.map +1 -1
  254. package/dist/processors/stripTrackedChanges.d.ts.map +1 -1
  255. package/dist/processors/stripTrackedChanges.js +16 -5
  256. package/dist/processors/stripTrackedChanges.js.map +1 -1
  257. package/dist/utils/deepClone.d.ts.map +1 -1
  258. package/dist/utils/deepClone.js +24 -11
  259. package/dist/utils/deepClone.js.map +1 -1
  260. package/dist/utils/units.d.ts.map +1 -1
  261. package/dist/utils/units.js +1 -1
  262. package/dist/utils/units.js.map +1 -1
  263. package/dist/utils/validation.d.ts.map +1 -1
  264. package/dist/utils/validation.js +11 -23
  265. package/dist/utils/validation.js.map +1 -1
  266. package/dist/utils/xmlSanitization.d.ts +2 -0
  267. package/dist/utils/xmlSanitization.d.ts.map +1 -1
  268. package/dist/utils/xmlSanitization.js +133 -7
  269. package/dist/utils/xmlSanitization.js.map +1 -1
  270. package/dist/validation/RevisionAutoFixer.d.ts +6 -2
  271. package/dist/validation/RevisionAutoFixer.d.ts.map +1 -1
  272. package/dist/validation/RevisionAutoFixer.js +68 -54
  273. package/dist/validation/RevisionAutoFixer.js.map +1 -1
  274. package/dist/validation/RevisionValidator.d.ts +2 -0
  275. package/dist/validation/RevisionValidator.d.ts.map +1 -1
  276. package/dist/validation/RevisionValidator.js +26 -21
  277. package/dist/validation/RevisionValidator.js.map +1 -1
  278. package/dist/xml/XMLBuilder.d.ts +1 -0
  279. package/dist/xml/XMLBuilder.d.ts.map +1 -1
  280. package/dist/xml/XMLBuilder.js +52 -20
  281. package/dist/xml/XMLBuilder.js.map +1 -1
  282. package/dist/xml/XMLParser.d.ts +1 -0
  283. package/dist/xml/XMLParser.d.ts.map +1 -1
  284. package/dist/xml/XMLParser.js +55 -13
  285. package/dist/xml/XMLParser.js.map +1 -1
  286. package/dist/zip/ZipHandler.d.ts.map +1 -1
  287. package/dist/zip/ZipHandler.js +37 -13
  288. package/dist/zip/ZipHandler.js.map +1 -1
  289. package/dist/zip/ZipReader.d.ts.map +1 -1
  290. package/dist/zip/ZipReader.js +52 -5
  291. package/dist/zip/ZipReader.js.map +1 -1
  292. package/dist/zip/errors.d.ts +3 -0
  293. package/dist/zip/errors.d.ts.map +1 -1
  294. package/dist/zip/errors.js +9 -1
  295. package/dist/zip/errors.js.map +1 -1
  296. package/dist/zip/types.d.ts +4 -0
  297. package/dist/zip/types.d.ts.map +1 -1
  298. package/dist/zip/types.js +4 -0
  299. package/dist/zip/types.js.map +1 -1
  300. package/package.json +2 -2
  301. package/src/core/Document.ts +1159 -240
  302. package/src/core/DocumentGenerator.ts +106 -26
  303. package/src/core/DocumentParser.ts +681 -114
  304. package/src/core/RelationshipManager.ts +446 -442
  305. package/src/elements/Comment.ts +123 -18
  306. package/src/elements/CommentManager.ts +121 -22
  307. package/src/elements/EndnoteManager.ts +4 -1
  308. package/src/elements/Field.ts +134 -43
  309. package/src/elements/FieldHelpers.ts +20 -4
  310. package/src/elements/Footer.ts +62 -67
  311. package/src/elements/FootnoteManager.ts +4 -1
  312. package/src/elements/Header.ts +62 -67
  313. package/src/elements/HeaderFooterManager.ts +56 -8
  314. package/src/elements/Hyperlink.ts +150 -55
  315. package/src/elements/Image.ts +51 -7
  316. package/src/elements/ImageManager.ts +24 -0
  317. package/src/elements/Paragraph.ts +161 -40
  318. package/src/elements/Revision.ts +446 -47
  319. package/src/elements/Run.ts +44 -6
  320. package/src/elements/Section.ts +25 -9
  321. package/src/elements/Shape.ts +52 -44
  322. package/src/elements/StructuredDocumentTag.ts +83 -8
  323. package/src/elements/Table.ts +151 -50
  324. package/src/elements/TableCell.ts +57 -17
  325. package/src/elements/TableOfContents.ts +10 -9
  326. package/src/elements/TableRow.ts +35 -3
  327. package/src/elements/TextBox.ts +58 -43
  328. package/src/formatting/AbstractNumbering.ts +4 -2
  329. package/src/formatting/NumberingLevel.ts +50 -12
  330. package/src/formatting/NumberingManager.ts +32 -3
  331. package/src/formatting/Style.ts +14 -0
  332. package/src/formatting/StylesManager.ts +29 -2
  333. package/src/helpers/CleanupHelper.ts +122 -30
  334. package/src/images/ImageOptimizer.ts +64 -5
  335. package/src/index.ts +7 -0
  336. package/src/internal.ts +1 -0
  337. package/src/processors/CompatibilityUpgrader.ts +15 -2
  338. package/src/processors/InMemoryRevisionAcceptor.ts +276 -17
  339. package/src/processors/RevisionWalker.ts +83 -7
  340. package/src/processors/SelectiveRevisionAcceptor.ts +164 -28
  341. package/src/processors/cnfStyleDecoder.ts +18 -12
  342. package/src/processors/stripTrackedChanges.ts +47 -10
  343. package/src/utils/deepClone.ts +48 -14
  344. package/src/utils/units.ts +3 -1
  345. package/src/utils/validation.ts +30 -32
  346. package/src/utils/xmlSanitization.ts +218 -22
  347. package/src/validation/RevisionAutoFixer.ts +109 -65
  348. package/src/validation/RevisionValidator.ts +48 -23
  349. package/src/xml/XMLBuilder.ts +82 -22
  350. package/src/xml/XMLParser.ts +87 -20
  351. package/src/zip/ZipHandler.ts +61 -21
  352. package/src/zip/ZipReader.ts +135 -13
  353. package/src/zip/errors.ts +15 -0
  354. package/src/zip/types.ts +44 -2
@@ -6,11 +6,12 @@
6
6
  */
7
7
 
8
8
  import { XMLElement } from '../xml/XMLBuilder.js';
9
- import { RunFormatting, FormFieldData } from './Run.js';
9
+ import { Run, RunFormatting, FormFieldData } from './Run.js';
10
10
  import {
11
11
  ParsedHyperlinkInstruction,
12
12
  parseHyperlinkInstruction,
13
13
  isHyperlinkInstruction,
14
+ escapeFieldArgument,
14
15
  } from './FieldHelpers.js';
15
16
  import type { Revision } from './Revision.js';
16
17
  import { pointsToHalfPoints } from '../utils/units.js';
@@ -74,6 +75,15 @@ export interface FieldProperties {
74
75
  dirty?: boolean;
75
76
  }
76
77
 
78
+ /**
79
+ * One run of a simple field's cached result: the text Word last computed
80
+ * for the field plus the run formatting it was displayed with.
81
+ */
82
+ export interface CachedFieldResultRun {
83
+ text: string;
84
+ formatting?: RunFormatting;
85
+ }
86
+
77
87
  /**
78
88
  * Represents a dynamic field
79
89
  */
@@ -83,6 +93,21 @@ export class Field {
83
93
  private formatting?: RunFormatting;
84
94
  private fldLock?: boolean;
85
95
  private dirty?: boolean;
96
+ /**
97
+ * Cached field result emitted as the fldSimple run text. For fields like
98
+ * HYPERLINK the visible text is NOT derivable from the instruction, so
99
+ * without this Word would display a generic placeholder until the user
100
+ * manually refreshes fields (F9).
101
+ */
102
+ private cachedResult?: string;
103
+ /**
104
+ * Per-run cached result parsed from a loaded `<w:fldSimple>`. Per
105
+ * ECMA-376 §17.16.16 CT_SimpleField, the child runs hold the field's
106
+ * current (cached) result — Word does not refresh fields on open by
107
+ * default, so discarding them replaces the user-visible text with a
108
+ * synthetic placeholder. Takes precedence over `cachedResult` in toXML().
109
+ */
110
+ private cachedResultRuns?: CachedFieldResultRun[];
86
111
 
87
112
  /**
88
113
  * Creates a new field
@@ -160,6 +185,36 @@ export class Field {
160
185
  return this.formatting;
161
186
  }
162
187
 
188
+ /**
189
+ * Sets the cached field result as plain text (single run; the field's
190
+ * own formatting applies). Clears any per-run cached result.
191
+ */
192
+ setCachedResult(text: string): this {
193
+ this.cachedResult = text;
194
+ this.cachedResultRuns = undefined;
195
+ return this;
196
+ }
197
+
198
+ /**
199
+ * Sets the cached field result as individual runs so per-run formatting
200
+ * from a parsed document survives round-trip.
201
+ */
202
+ setCachedResultRuns(runs: CachedFieldResultRun[]): this {
203
+ this.cachedResultRuns = runs.length > 0 ? [...runs] : undefined;
204
+ return this;
205
+ }
206
+
207
+ /**
208
+ * Gets the cached field result text (concatenated across runs), or
209
+ * undefined when the field has no cached result.
210
+ */
211
+ getCachedResult(): string | undefined {
212
+ if (this.cachedResultRuns) {
213
+ return this.cachedResultRuns.map((run) => run.text).join('');
214
+ }
215
+ return this.cachedResult;
216
+ }
217
+
163
218
  /**
164
219
  * Checks if this field is a HYPERLINK field
165
220
  * @returns True if the field type is HYPERLINK or instruction starts with HYPERLINK
@@ -190,21 +245,49 @@ export class Field {
190
245
  * The fldSimple element should be added directly to paragraph children (not wrapped in w:r).
191
246
  */
192
247
  toXML(): XMLElement {
193
- // Build the inner run with optional formatting
194
- const runChildren: XMLElement[] = [];
195
- if (this.formatting) {
196
- runChildren.push(this.createRunProperties());
248
+ let resultRuns: XMLElement[];
249
+ if (this.cachedResultRuns) {
250
+ // Re-emit the parsed cached result runs verbatim: per ECMA-376
251
+ // §17.16.16 they ARE the field's current result, and Word does not
252
+ // refresh fields on open by default — substituting a placeholder
253
+ // would corrupt the visible content. Field-level formatting (e.g.
254
+ // setColor after parse) is layered over each run's own formatting
255
+ // so programmatic styling still takes effect.
256
+ resultRuns = this.cachedResultRuns.map((cached) => {
257
+ const children: XMLElement[] = [];
258
+ const merged =
259
+ cached.formatting || this.formatting
260
+ ? { ...cached.formatting, ...this.formatting }
261
+ : undefined;
262
+ const rPr = merged ? Run.generateRunPropertiesXML(merged) : null;
263
+ if (rPr) {
264
+ children.push(rPr);
265
+ }
266
+ children.push({
267
+ name: 'w:t',
268
+ attributes: { 'xml:space': 'preserve' },
269
+ children: [cached.text],
270
+ });
271
+ return { name: 'w:r', children };
272
+ });
273
+ } else {
274
+ // Build the inner run with optional formatting
275
+ const runChildren: XMLElement[] = [];
276
+ if (this.formatting) {
277
+ runChildren.push(this.createRunProperties());
278
+ }
279
+ // Per ECMA-376 §22.1.2.33 CT_Text, `xml:space="preserve"` is the
280
+ // standard sentinel that stops XML processors from collapsing leading /
281
+ // trailing whitespace in the text content. Run.ts always emits it;
282
+ // emit it here too so field placeholders with spaces (e.g. localized
283
+ // date formats like "1 Jan 2026") survive XML round-trip intact.
284
+ runChildren.push({
285
+ name: 'w:t',
286
+ attributes: { 'xml:space': 'preserve' },
287
+ children: [this.cachedResult ?? this.getPlaceholderText()],
288
+ });
289
+ resultRuns = [{ name: 'w:r', children: runChildren }];
197
290
  }
198
- // Per ECMA-376 §22.1.2.33 CT_Text, `xml:space="preserve"` is the
199
- // standard sentinel that stops XML processors from collapsing leading /
200
- // trailing whitespace in the text content. Run.ts always emits it;
201
- // emit it here too so field placeholders with spaces (e.g. localized
202
- // date formats like "1 Jan 2026") survive XML round-trip intact.
203
- runChildren.push({
204
- name: 'w:t',
205
- attributes: { 'xml:space': 'preserve' },
206
- children: [this.getPlaceholderText()],
207
- });
208
291
 
209
292
  // CT_SimpleField (§17.16.16) carries two ST_OnOff attributes beyond
210
293
  // the required w:instr: w:fldLock (locked against updates) and
@@ -224,12 +307,7 @@ export class Field {
224
307
  return {
225
308
  name: 'w:fldSimple',
226
309
  attributes: fldSimpleAttrs,
227
- children: [
228
- {
229
- name: 'w:r',
230
- children: runChildren,
231
- },
232
- ],
310
+ children: resultRuns,
233
311
  };
234
312
  }
235
313
 
@@ -481,23 +559,25 @@ export class Field {
481
559
  */
482
560
  static createHyperlink(
483
561
  url: string,
484
- _displayText: string = url,
562
+ displayText: string = url,
485
563
  tooltip?: string,
486
564
  formatting?: RunFormatting
487
565
  ): Field {
488
- let instruction = `HYPERLINK "${url}"`;
566
+ let instruction = `HYPERLINK "${escapeFieldArgument(url)}"`;
489
567
 
490
568
  if (tooltip) {
491
- instruction += ` \\o "${tooltip}"`;
569
+ instruction += ` \\o "${escapeFieldArgument(tooltip)}"`;
492
570
  }
493
571
 
494
572
  instruction += ' \\* MERGEFORMAT';
495
573
 
496
- return new Field({
574
+ const field = new Field({
497
575
  type: 'HYPERLINK',
498
576
  instruction,
499
577
  formatting,
500
578
  });
579
+ field.cachedResult = displayText;
580
+ return field;
501
581
  }
502
582
 
503
583
  /**
@@ -535,7 +615,7 @@ export class Field {
535
615
  throw new Error('TC level must be between 1 and 9');
536
616
  }
537
617
 
538
- const instruction = `TC "${text}" \\f C \\l ${level}`;
618
+ const instruction = `TC "${escapeFieldArgument(text)}" \\f C \\l ${level}`;
539
619
 
540
620
  return new Field({
541
621
  type: 'TC',
@@ -551,11 +631,12 @@ export class Field {
551
631
  * @param formatting Optional run formatting
552
632
  */
553
633
  static createXEEntry(text: string, subEntry?: string, formatting?: RunFormatting): Field {
554
- let instruction = `XE "${text}"`;
555
-
556
- if (subEntry) {
557
- instruction += `:${subEntry}`;
558
- }
634
+ // Per ECMA-376 §17.16.5.75 the main-entry/subentry colon separator
635
+ // belongs INSIDE the quoted field-argument ({ XE "Main:Sub" }) —
636
+ // text after the closing quote would be misparsed as stray switches.
637
+ const instruction = subEntry
638
+ ? `XE "${escapeFieldArgument(text)}:${escapeFieldArgument(subEntry)}"`
639
+ : `XE "${escapeFieldArgument(text)}"`;
559
640
 
560
641
  return new Field({
561
642
  type: 'XE',
@@ -571,7 +652,7 @@ export class Field {
571
652
  */
572
653
  static createCustom(instruction: string, formatting?: RunFormatting): Field {
573
654
  return new Field({
574
- type: 'PAGE', // Placeholder type
655
+ type: 'CUSTOM',
575
656
  instruction,
576
657
  formatting,
577
658
  });
@@ -1120,17 +1201,27 @@ export class ComplexField {
1120
1201
  runs.push(...nestedField.toXML());
1121
1202
  }
1122
1203
 
1123
- // 3. Separator run
1124
- runs.push({
1125
- name: 'w:r',
1126
- children: [
1127
- {
1128
- name: 'w:fldChar',
1129
- attributes: { 'w:fldCharType': 'separate' },
1130
- selfClosing: true,
1131
- },
1132
- ],
1133
- });
1204
+ // 3. Separator run — only when the field carries a result section.
1205
+ // Per ECMA-376, fields without results (XE/TC markers, never-updated
1206
+ // fields) skip w:fldSep entirely; synthesizing one would break
1207
+ // round-trip fidelity for parsed separator-less fields.
1208
+ const emitsResultSection =
1209
+ this._hasResultSection ||
1210
+ !!this.result ||
1211
+ this.resultContent.length > 0 ||
1212
+ this.resultRevisions.length > 0;
1213
+ if (emitsResultSection) {
1214
+ runs.push({
1215
+ name: 'w:r',
1216
+ children: [
1217
+ {
1218
+ name: 'w:fldChar',
1219
+ attributes: { 'w:fldCharType': 'separate' },
1220
+ selfClosing: true,
1221
+ },
1222
+ ],
1223
+ });
1224
+ }
1134
1225
 
1135
1226
  // 4. Result content (prioritize custom XML content, then simple text)
1136
1227
  // Design note: For INCLUDEPICTURE fields, the parser stores the w:drawing
@@ -133,6 +133,22 @@ export function parseHyperlinkInstruction(instruction: string): ParsedHyperlinkI
133
133
  };
134
134
  }
135
135
 
136
+ /**
137
+ * Escapes a value for use inside a quoted field-instruction argument.
138
+ *
139
+ * Per ECMA-376 field-argument syntax (§17.16.4.1), a literal double quote
140
+ * inside a quoted argument must be written as \" and a literal backslash
141
+ * as \\ — otherwise an embedded quote terminates the argument early and
142
+ * the remainder is misparsed as stray switches. The XML layer only
143
+ * escapes XML entities, so this must happen at field-code level.
144
+ *
145
+ * @param value Raw argument text
146
+ * @returns Text safe to interpolate between double quotes in a field code
147
+ */
148
+ export function escapeFieldArgument(value: string): string {
149
+ return value.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
150
+ }
151
+
136
152
  /**
137
153
  * Creates a HYPERLINK field instruction string from components
138
154
  *
@@ -148,14 +164,14 @@ export function parseHyperlinkInstruction(instruction: string): ParsedHyperlinkI
148
164
  * ```
149
165
  */
150
166
  export function buildHyperlinkInstruction(url: string, anchor?: string, tooltip?: string): string {
151
- let instruction = `HYPERLINK "${url}"`;
167
+ let instruction = `HYPERLINK "${escapeFieldArgument(url)}"`;
152
168
 
153
169
  if (anchor) {
154
- instruction += ` \\l "${anchor}"`;
170
+ instruction += ` \\l "${escapeFieldArgument(anchor)}"`;
155
171
  }
156
172
 
157
173
  if (tooltip) {
158
- instruction += ` \\o "${tooltip}"`;
174
+ instruction += ` \\o "${escapeFieldArgument(tooltip)}"`;
159
175
  }
160
176
 
161
177
  // Always add \h switch for clickable hyperlinks
@@ -291,7 +307,7 @@ export function createIFField(
291
307
  trueContent: string,
292
308
  falseContent = ''
293
309
  ): ComplexField {
294
- const instruction = ` IF ${condition} "${trueContent}" "${falseContent}" `;
310
+ const instruction = ` IF ${condition} "${escapeFieldArgument(trueContent)}" "${escapeFieldArgument(falseContent)}" `;
295
311
 
296
312
  return new ComplexField({
297
313
  instruction,
@@ -5,7 +5,8 @@
5
5
  * Different footers can be defined for first page, odd pages, and even pages.
6
6
  */
7
7
 
8
- import { XMLElement } from '../xml/XMLBuilder.js';
8
+ import { XMLBuilder } from '../xml/XMLBuilder.js';
9
+ import { XMLParser } from '../xml/XMLParser.js';
9
10
  import { Paragraph } from './Paragraph.js';
10
11
  import { RunFormatting } from './Run.js';
11
12
  import { Table } from './Table.js';
@@ -36,6 +37,12 @@ export class Footer {
36
37
  private type: FooterType;
37
38
  private footerId?: string;
38
39
  private rawXML?: string; // Store original XML for preservation
40
+ // Top-level w:p/w:tbl count of the preserved raw XML. DocumentParser calls
41
+ // setRawXML() before repopulating elements from that same XML, so mutators
42
+ // can't simply clear rawXML — they would fire during parsing and lose the
43
+ // preserved bytes. Instead, growth beyond this count is the signal that
44
+ // content was added after load and the preserved XML is stale.
45
+ private rawXmlBlockCount = Number.MAX_SAFE_INTEGER;
39
46
 
40
47
  /**
41
48
  * Creates a new footer
@@ -51,9 +58,43 @@ export class Footer {
51
58
  */
52
59
  setRawXML(xml: string): this {
53
60
  this.rawXML = xml;
61
+ this.rawXmlBlockCount = Footer.countTopLevelBlocks(xml);
54
62
  return this;
55
63
  }
56
64
 
65
+ /**
66
+ * Counts the top-level w:p/w:tbl children of a raw footer part.
67
+ * Parsing populates this.elements from exactly those blocks, so the count
68
+ * is the baseline against which mutators detect post-load additions.
69
+ */
70
+ private static countTopLevelBlocks(xml: string): number {
71
+ try {
72
+ const parsed = XMLParser.parseToObject(xml) as Record<string, unknown>;
73
+ const root = parsed?.['w:ftr'];
74
+ if (!root || typeof root !== 'object') {
75
+ return 0;
76
+ }
77
+ const count = (node: unknown): number =>
78
+ node === undefined ? 0 : Array.isArray(node) ? node.length : 1;
79
+ const rootObj = root as Record<string, unknown>;
80
+ return count(rootObj['w:p']) + count(rootObj['w:tbl']);
81
+ } catch {
82
+ // Unparseable part: never invalidate, keep the preserved bytes
83
+ return Number.MAX_SAFE_INTEGER;
84
+ }
85
+ }
86
+
87
+ /**
88
+ * Drops the preserved raw XML once content grows beyond what that XML
89
+ * already contains, so toXML() regenerates from elements and post-load
90
+ * edits are not silently discarded on save.
91
+ */
92
+ private invalidateRawXmlOnGrowth(): void {
93
+ if (this.rawXML !== undefined && this.elements.length > this.rawXmlBlockCount) {
94
+ this.rawXML = undefined;
95
+ }
96
+ }
97
+
57
98
  /**
58
99
  * Gets the raw XML content if available
59
100
  */
@@ -90,6 +131,7 @@ export class Footer {
90
131
  */
91
132
  addParagraph(paragraph: Paragraph): this {
92
133
  this.elements.push(paragraph);
134
+ this.invalidateRawXmlOnGrowth();
93
135
  return this;
94
136
  }
95
137
 
@@ -103,6 +145,7 @@ export class Footer {
103
145
  para.addText(text);
104
146
  }
105
147
  this.elements.push(para);
148
+ this.invalidateRawXmlOnGrowth();
106
149
  return para;
107
150
  }
108
151
 
@@ -125,6 +168,7 @@ export class Footer {
125
168
  const para = new Paragraph();
126
169
  para.addText(text, formatting);
127
170
  this.elements.push(para);
171
+ this.invalidateRawXmlOnGrowth();
128
172
  return para;
129
173
  }
130
174
 
@@ -134,6 +178,7 @@ export class Footer {
134
178
  */
135
179
  addTable(table: Table): this {
136
180
  this.elements.push(table);
181
+ this.invalidateRawXmlOnGrowth();
137
182
  return this;
138
183
  }
139
184
 
@@ -145,6 +190,7 @@ export class Footer {
145
190
  createTable(rows: number, columns: number): Table {
146
191
  const table = new Table(rows, columns);
147
192
  this.elements.push(table);
193
+ this.invalidateRawXmlOnGrowth();
148
194
  return table;
149
195
  }
150
196
 
@@ -181,76 +227,25 @@ export class Footer {
181
227
  return this.rawXML;
182
228
  }
183
229
 
184
- // Otherwise generate from elements
185
- const elementXmls = this.elements.map((el) => el.toXML());
186
-
187
- let xml = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n';
188
- xml += '<w:ftr xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" ';
189
- xml += 'xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">\n';
190
-
191
- // Add elements
192
- for (const element of elementXmls) {
193
- xml += this.renderElement(element, 1);
194
- }
230
+ // Serialize through XMLBuilder so text and attribute values are
231
+ // XML-escaped and rawXml passthrough children are honored
232
+ const children = this.elements.map((el) => el.toXML());
195
233
 
196
- // If no elements, add an empty paragraph
197
- if (this.elements.length === 0) {
198
- xml += ' <w:p/>\n';
234
+ // ECMA-376 requires block-level content in w:ftr
235
+ if (children.length === 0) {
236
+ children.push(XMLBuilder.w('p'));
199
237
  }
200
238
 
201
- xml += '</w:ftr>';
202
-
203
- return xml;
204
- }
205
-
206
- /**
207
- * Renders an XML element to string with indentation
208
- */
209
- private renderElement(element: XMLElement, indent: number): string {
210
- const spaces = ' '.repeat(indent);
211
- let xml = '';
212
-
213
- if (element.selfClosing) {
214
- xml += `${spaces}<${element.name}`;
215
- if (element.attributes) {
216
- for (const [key, value] of Object.entries(element.attributes)) {
217
- xml += ` ${key}="${value}"`;
218
- }
219
- }
220
- xml += '/>\n';
221
- } else {
222
- xml += `${spaces}<${element.name}`;
223
- if (element.attributes) {
224
- for (const [key, value] of Object.entries(element.attributes)) {
225
- xml += ` ${key}="${value}"`;
226
- }
227
- }
228
- xml += '>';
229
-
230
- if (element.children && element.children.length > 0) {
231
- const hasOnlyText = element.children.every((c) => typeof c === 'string');
232
-
233
- if (hasOnlyText) {
234
- // Inline text content
235
- xml += element.children.join('');
236
- } else {
237
- // Block-level children
238
- xml += '\n';
239
- for (const child of element.children) {
240
- if (typeof child === 'string') {
241
- xml += spaces + ' ' + child + '\n';
242
- } else {
243
- xml += this.renderElement(child, indent + 1);
244
- }
245
- }
246
- xml += spaces;
247
- }
248
- }
249
-
250
- xml += `</${element.name}>\n`;
251
- }
239
+ const namespaces: Record<string, string> = {
240
+ ...XMLBuilder.createNamespaces(),
241
+ // Extension namespaces (w14:paraId etc. on regenerated paragraphs)
242
+ // must be declared ignorable or Word rejects the part
243
+ 'mc:Ignorable': 'w14 w15 wp14 asvg',
244
+ };
252
245
 
253
- return xml;
246
+ const builder = new XMLBuilder();
247
+ builder.element('w:ftr', namespaces, children);
248
+ return builder.build(true);
254
249
  }
255
250
 
256
251
  /**
@@ -130,7 +130,10 @@ export class FootnoteManager {
130
130
  }
131
131
 
132
132
  /**
133
- * Removes a footnote
133
+ * Removes a footnote from the model only. Does not touch body
134
+ * w:footnoteReference runs or the document's dirty flags — callers going
135
+ * through a Document should use Document.removeFootnote(), which also
136
+ * strips the matching references and forces footnotes.xml regeneration.
134
137
  * @param id Footnote ID
135
138
  * @returns True if removed, false if not found or if it's a special type
136
139
  */