@linxiraos/pi-utils 1.0.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.
Files changed (244) hide show
  1. package/CHANGELOG.md +449 -0
  2. package/README.md +36 -0
  3. package/dist/types/abortable.d.ts +32 -0
  4. package/dist/types/acp/connection.d.ts +118 -0
  5. package/dist/types/acp/protocol.d.ts +526 -0
  6. package/dist/types/acp/schema.d.ts +41 -0
  7. package/dist/types/acp/stream.d.ts +8 -0
  8. package/dist/types/acp/transport.d.ts +81 -0
  9. package/dist/types/acp.d.ts +6 -0
  10. package/dist/types/async.d.ts +20 -0
  11. package/dist/types/binary.d.ts +17 -0
  12. package/dist/types/browsers.d.ts +68 -0
  13. package/dist/types/chalk.d.ts +125 -0
  14. package/dist/types/cli.d.ts +134 -0
  15. package/dist/types/color.d.ts +102 -0
  16. package/dist/types/dates.d.ts +7 -0
  17. package/dist/types/dirs.d.ts +282 -0
  18. package/dist/types/docx/converter.d.ts +46 -0
  19. package/dist/types/docx/xml.d.ts +26 -0
  20. package/dist/types/docx/zip.d.ts +6 -0
  21. package/dist/types/docx.d.ts +11 -0
  22. package/dist/types/dom/core.d.ts +431 -0
  23. package/dist/types/dom/parser.d.ts +7 -0
  24. package/dist/types/dom/selector.d.ts +5 -0
  25. package/dist/types/dom.d.ts +5 -0
  26. package/dist/types/env.d.ts +124 -0
  27. package/dist/types/fetch-retry.d.ts +96 -0
  28. package/dist/types/file-lock.d.ts +23 -0
  29. package/dist/types/format.d.ts +37 -0
  30. package/dist/types/frontmatter.d.ts +46 -0
  31. package/dist/types/fs-error.d.ts +31 -0
  32. package/dist/types/glob.d.ts +28 -0
  33. package/dist/types/headers.d.ts +34 -0
  34. package/dist/types/index.d.ts +39 -0
  35. package/dist/types/json-parse.d.ts +74 -0
  36. package/dist/types/json.d.ts +14 -0
  37. package/dist/types/logger/rotating-file.d.ts +18 -0
  38. package/dist/types/logger.d.ts +96 -0
  39. package/dist/types/loop-phase.d.ts +10 -0
  40. package/dist/types/lru.d.ts +46 -0
  41. package/dist/types/marked/core.d.ts +445 -0
  42. package/dist/types/marked.d.ts +2 -0
  43. package/dist/types/mermaid-ascii.d.ts +11 -0
  44. package/dist/types/mime.d.ts +29 -0
  45. package/dist/types/module-timer.d.ts +1 -0
  46. package/dist/types/path-tree.d.ts +76 -0
  47. package/dist/types/path.d.ts +2 -0
  48. package/dist/types/peek-file.d.ts +29 -0
  49. package/dist/types/postmortem.d.ts +104 -0
  50. package/dist/types/process-name.d.ts +7 -0
  51. package/dist/types/procmgr.d.ts +74 -0
  52. package/dist/types/prompt.d.ts +18 -0
  53. package/dist/types/ptree.d.ts +110 -0
  54. package/dist/types/readability/readability.d.ts +9 -0
  55. package/dist/types/readability/readerable.d.ts +10 -0
  56. package/dist/types/readability/types.d.ts +70 -0
  57. package/dist/types/readability.d.ts +4 -0
  58. package/dist/types/ring.d.ts +93 -0
  59. package/dist/types/runtime-install.d.ts +78 -0
  60. package/dist/types/sanitize-text.d.ts +29 -0
  61. package/dist/types/snowflake.d.ts +25 -0
  62. package/dist/types/stderr-guard.d.ts +22 -0
  63. package/dist/types/stream.d.ts +67 -0
  64. package/dist/types/tab-spacing.d.ts +24 -0
  65. package/dist/types/temp.d.ts +17 -0
  66. package/dist/types/template.d.ts +62 -0
  67. package/dist/types/timing-buffer.d.ts +22 -0
  68. package/dist/types/tls-fetch.d.ts +37 -0
  69. package/dist/types/turndown/gfm.d.ts +11 -0
  70. package/dist/types/turndown/html.d.ts +5 -0
  71. package/dist/types/turndown/service.d.ts +21 -0
  72. package/dist/types/turndown/types.d.ts +70 -0
  73. package/dist/types/turndown.d.ts +4 -0
  74. package/dist/types/type-guards.d.ts +5 -0
  75. package/dist/types/vendor/mermaid-ascii/ascii/ansi.d.ts +41 -0
  76. package/dist/types/vendor/mermaid-ascii/ascii/canvas.d.ts +89 -0
  77. package/dist/types/vendor/mermaid-ascii/ascii/class-diagram.d.ts +7 -0
  78. package/dist/types/vendor/mermaid-ascii/ascii/converter.d.ts +12 -0
  79. package/dist/types/vendor/mermaid-ascii/ascii/draw.d.ts +66 -0
  80. package/dist/types/vendor/mermaid-ascii/ascii/edge-bundling.d.ts +48 -0
  81. package/dist/types/vendor/mermaid-ascii/ascii/edge-routing.d.ts +43 -0
  82. package/dist/types/vendor/mermaid-ascii/ascii/er-diagram.d.ts +7 -0
  83. package/dist/types/vendor/mermaid-ascii/ascii/grid.d.ts +56 -0
  84. package/dist/types/vendor/mermaid-ascii/ascii/index.d.ts +65 -0
  85. package/dist/types/vendor/mermaid-ascii/ascii/multiline-utils.d.ts +27 -0
  86. package/dist/types/vendor/mermaid-ascii/ascii/pathfinder.d.ts +17 -0
  87. package/dist/types/vendor/mermaid-ascii/ascii/sequence.d.ts +7 -0
  88. package/dist/types/vendor/mermaid-ascii/ascii/shapes/circle.d.ts +11 -0
  89. package/dist/types/vendor/mermaid-ascii/ascii/shapes/corners.d.ts +34 -0
  90. package/dist/types/vendor/mermaid-ascii/ascii/shapes/diamond.d.ts +11 -0
  91. package/dist/types/vendor/mermaid-ascii/ascii/shapes/hexagon.d.ts +11 -0
  92. package/dist/types/vendor/mermaid-ascii/ascii/shapes/index.d.ts +26 -0
  93. package/dist/types/vendor/mermaid-ascii/ascii/shapes/rectangle.d.ts +31 -0
  94. package/dist/types/vendor/mermaid-ascii/ascii/shapes/rounded.d.ts +11 -0
  95. package/dist/types/vendor/mermaid-ascii/ascii/shapes/special.d.ts +59 -0
  96. package/dist/types/vendor/mermaid-ascii/ascii/shapes/stadium.d.ts +17 -0
  97. package/dist/types/vendor/mermaid-ascii/ascii/shapes/state.d.ts +30 -0
  98. package/dist/types/vendor/mermaid-ascii/ascii/shapes/types.d.ts +55 -0
  99. package/dist/types/vendor/mermaid-ascii/ascii/types.d.ts +206 -0
  100. package/dist/types/vendor/mermaid-ascii/ascii/validate.d.ts +51 -0
  101. package/dist/types/vendor/mermaid-ascii/ascii/xychart.d.ts +2 -0
  102. package/dist/types/vendor/mermaid-ascii/class/parser.d.ts +6 -0
  103. package/dist/types/vendor/mermaid-ascii/class/types.d.ts +102 -0
  104. package/dist/types/vendor/mermaid-ascii/er/parser.d.ts +6 -0
  105. package/dist/types/vendor/mermaid-ascii/er/types.d.ts +76 -0
  106. package/dist/types/vendor/mermaid-ascii/index.d.ts +1 -0
  107. package/dist/types/vendor/mermaid-ascii/multiline-utils.d.ts +9 -0
  108. package/dist/types/vendor/mermaid-ascii/parser.d.ts +7 -0
  109. package/dist/types/vendor/mermaid-ascii/sequence/parser.d.ts +6 -0
  110. package/dist/types/vendor/mermaid-ascii/sequence/types.d.ts +130 -0
  111. package/dist/types/vendor/mermaid-ascii/text-metrics.d.ts +21 -0
  112. package/dist/types/vendor/mermaid-ascii/types.d.ts +114 -0
  113. package/dist/types/vendor/mermaid-ascii/xychart/colors.d.ts +25 -0
  114. package/dist/types/vendor/mermaid-ascii/xychart/parser.d.ts +6 -0
  115. package/dist/types/vendor/mermaid-ascii/xychart/types.d.ts +145 -0
  116. package/dist/types/version.d.ts +18 -0
  117. package/dist/types/vterm/buffer.d.ts +99 -0
  118. package/dist/types/vterm/terminal.d.ts +44 -0
  119. package/dist/types/vterm.d.ts +8 -0
  120. package/dist/types/which.d.ts +37 -0
  121. package/dist/types/worker-host.d.ts +51 -0
  122. package/dist/types/xml.d.ts +31 -0
  123. package/package.json +59 -0
  124. package/src/abortable.ts +109 -0
  125. package/src/acp/connection.ts +344 -0
  126. package/src/acp/protocol.ts +466 -0
  127. package/src/acp/schema.ts +160 -0
  128. package/src/acp/stream.ts +82 -0
  129. package/src/acp/transport.ts +213 -0
  130. package/src/acp.ts +6 -0
  131. package/src/async.ts +91 -0
  132. package/src/binary.ts +50 -0
  133. package/src/browsers.ts +501 -0
  134. package/src/chalk.ts +312 -0
  135. package/src/cli.ts +529 -0
  136. package/src/color.ts +302 -0
  137. package/src/dates.ts +194 -0
  138. package/src/dirs.ts +1026 -0
  139. package/src/docx/converter.ts +681 -0
  140. package/src/docx/xml.ts +166 -0
  141. package/src/docx/zip.ts +87 -0
  142. package/src/docx.ts +20 -0
  143. package/src/dom/core.ts +1254 -0
  144. package/src/dom/parser.ts +370 -0
  145. package/src/dom/selector.ts +290 -0
  146. package/src/dom.ts +33 -0
  147. package/src/env.ts +400 -0
  148. package/src/fetch-retry.ts +379 -0
  149. package/src/file-lock.ts +69 -0
  150. package/src/format.ts +113 -0
  151. package/src/frontmatter.ts +205 -0
  152. package/src/fs-error.ts +56 -0
  153. package/src/glob.ts +189 -0
  154. package/src/headers.ts +167 -0
  155. package/src/index.ts +59 -0
  156. package/src/json-parse.ts +821 -0
  157. package/src/json.ts +23 -0
  158. package/src/logger/rotating-file.ts +149 -0
  159. package/src/logger.ts +798 -0
  160. package/src/loop-phase.ts +49 -0
  161. package/src/lru.ts +185 -0
  162. package/src/marked/core.ts +1576 -0
  163. package/src/marked.ts +2 -0
  164. package/src/mermaid-ascii.ts +31 -0
  165. package/src/mime.ts +159 -0
  166. package/src/module-timer.ts +148 -0
  167. package/src/path-tree.ts +147 -0
  168. package/src/path.ts +28 -0
  169. package/src/peek-file.ts +188 -0
  170. package/src/postmortem.ts +433 -0
  171. package/src/process-name.ts +57 -0
  172. package/src/procmgr.ts +247 -0
  173. package/src/prompt.ts +545 -0
  174. package/src/ptree.ts +412 -0
  175. package/src/readability/readability.ts +533 -0
  176. package/src/readability/readerable.ts +51 -0
  177. package/src/readability/types.ts +72 -0
  178. package/src/readability.ts +11 -0
  179. package/src/ring.ts +169 -0
  180. package/src/runtime-install.ts +393 -0
  181. package/src/sanitize-text.ts +96 -0
  182. package/src/snowflake.ts +121 -0
  183. package/src/stderr-guard.ts +155 -0
  184. package/src/stream.ts +454 -0
  185. package/src/tab-spacing.ts +336 -0
  186. package/src/temp.ts +134 -0
  187. package/src/template.ts +586 -0
  188. package/src/timing-buffer.ts +47 -0
  189. package/src/tls-fetch.ts +178 -0
  190. package/src/turndown/gfm.ts +106 -0
  191. package/src/turndown/html.ts +257 -0
  192. package/src/turndown/service.ts +334 -0
  193. package/src/turndown/types.ts +81 -0
  194. package/src/turndown.ts +5 -0
  195. package/src/type-guards.ts +17 -0
  196. package/src/vendor/mermaid-ascii/NOTICE +33 -0
  197. package/src/vendor/mermaid-ascii/ascii/ansi.ts +409 -0
  198. package/src/vendor/mermaid-ascii/ascii/canvas.ts +476 -0
  199. package/src/vendor/mermaid-ascii/ascii/class-diagram.ts +699 -0
  200. package/src/vendor/mermaid-ascii/ascii/converter.ts +271 -0
  201. package/src/vendor/mermaid-ascii/ascii/draw.ts +1382 -0
  202. package/src/vendor/mermaid-ascii/ascii/edge-bundling.ts +328 -0
  203. package/src/vendor/mermaid-ascii/ascii/edge-routing.ts +297 -0
  204. package/src/vendor/mermaid-ascii/ascii/er-diagram.ts +441 -0
  205. package/src/vendor/mermaid-ascii/ascii/grid.ts +578 -0
  206. package/src/vendor/mermaid-ascii/ascii/index.ts +187 -0
  207. package/src/vendor/mermaid-ascii/ascii/multiline-utils.ts +78 -0
  208. package/src/vendor/mermaid-ascii/ascii/pathfinder.ts +277 -0
  209. package/src/vendor/mermaid-ascii/ascii/sequence.ts +460 -0
  210. package/src/vendor/mermaid-ascii/ascii/shapes/circle.ts +27 -0
  211. package/src/vendor/mermaid-ascii/ascii/shapes/corners.ts +127 -0
  212. package/src/vendor/mermaid-ascii/ascii/shapes/diamond.ts +27 -0
  213. package/src/vendor/mermaid-ascii/ascii/shapes/hexagon.ts +27 -0
  214. package/src/vendor/mermaid-ascii/ascii/shapes/index.ts +101 -0
  215. package/src/vendor/mermaid-ascii/ascii/shapes/rectangle.ts +175 -0
  216. package/src/vendor/mermaid-ascii/ascii/shapes/rounded.ts +27 -0
  217. package/src/vendor/mermaid-ascii/ascii/shapes/special.ts +296 -0
  218. package/src/vendor/mermaid-ascii/ascii/shapes/stadium.ts +114 -0
  219. package/src/vendor/mermaid-ascii/ascii/shapes/state.ts +192 -0
  220. package/src/vendor/mermaid-ascii/ascii/shapes/types.ts +73 -0
  221. package/src/vendor/mermaid-ascii/ascii/types.ts +273 -0
  222. package/src/vendor/mermaid-ascii/ascii/validate.ts +120 -0
  223. package/src/vendor/mermaid-ascii/ascii/xychart.ts +875 -0
  224. package/src/vendor/mermaid-ascii/class/parser.ts +290 -0
  225. package/src/vendor/mermaid-ascii/class/types.ts +121 -0
  226. package/src/vendor/mermaid-ascii/er/parser.ts +181 -0
  227. package/src/vendor/mermaid-ascii/er/types.ts +91 -0
  228. package/src/vendor/mermaid-ascii/index.ts +14 -0
  229. package/src/vendor/mermaid-ascii/multiline-utils.ts +30 -0
  230. package/src/vendor/mermaid-ascii/parser.ts +645 -0
  231. package/src/vendor/mermaid-ascii/sequence/parser.ts +207 -0
  232. package/src/vendor/mermaid-ascii/sequence/types.ts +146 -0
  233. package/src/vendor/mermaid-ascii/text-metrics.ts +71 -0
  234. package/src/vendor/mermaid-ascii/types.ts +164 -0
  235. package/src/vendor/mermaid-ascii/xychart/colors.ts +140 -0
  236. package/src/vendor/mermaid-ascii/xychart/parser.ts +115 -0
  237. package/src/vendor/mermaid-ascii/xychart/types.ts +150 -0
  238. package/src/version.ts +99 -0
  239. package/src/vterm/buffer.ts +218 -0
  240. package/src/vterm/terminal.ts +773 -0
  241. package/src/vterm.ts +8 -0
  242. package/src/which.ts +234 -0
  243. package/src/worker-host.ts +100 -0
  244. package/src/xml.ts +313 -0
@@ -0,0 +1,681 @@
1
+ import * as fs from "node:fs/promises";
2
+ import * as path from "node:path";
3
+ import { attribute, childElements, descendants, firstChild, localName, parseXml, type XmlElement } from "./xml";
4
+ import { readZip, readZipText, type ZipEntries } from "./zip";
5
+
6
+ /** A mammoth-compatible diagnostic emitted while converting a document. */
7
+ export interface DocxMessage {
8
+ readonly type: "warning" | "error";
9
+ readonly message: string;
10
+ }
11
+
12
+ /** The HTML and diagnostics produced by a DOCX conversion. */
13
+ export interface DocxResult {
14
+ readonly value: string;
15
+ readonly messages: DocxMessage[];
16
+ }
17
+
18
+ /** An in-memory or filesystem DOCX input. */
19
+ export type DocxInput =
20
+ | { readonly buffer: Uint8Array; readonly path?: never }
21
+ | { readonly path: string; readonly buffer?: never };
22
+
23
+ /** An image exposed to a custom image converter. */
24
+ export interface DocxImage {
25
+ readonly contentType: string;
26
+ readonly altText: string;
27
+ /** Read the image payload using mammoth's used encoding surface. */
28
+ read(encoding: "base64"): Promise<string>;
29
+ }
30
+
31
+ /** HTML attributes returned by a custom image converter. */
32
+ export type ImageAttributes = Readonly<Record<string, string>>;
33
+
34
+ /** A callback that maps an embedded DOCX image to HTML attributes. */
35
+ export type ImageAttributeConverter = (image: DocxImage) => ImageAttributes | Promise<ImageAttributes>;
36
+
37
+ /** An image converter created by `images.imgElement`. */
38
+ export interface ImageConverter {
39
+ readonly convert: ImageAttributeConverter;
40
+ }
41
+
42
+ /** Options supported by the behavior-compatible DOCX converter. */
43
+ export interface ConvertToHtmlOptions {
44
+ readonly convertImage?: ImageConverter;
45
+ readonly styleMap?: string | readonly string[];
46
+ readonly includeDefaultStyleMap?: boolean;
47
+ }
48
+
49
+ /** Mammoth-shaped helpers for configuring embedded image conversion. */
50
+ export const images = {
51
+ /** Wrap an image-to-attributes callback for `convertToHtml`. */
52
+ imgElement(convert: ImageAttributeConverter): ImageConverter {
53
+ return { convert };
54
+ },
55
+ };
56
+
57
+ interface Relationship {
58
+ readonly target: string;
59
+ readonly type: string;
60
+ readonly external: boolean;
61
+ }
62
+
63
+ interface Formatting {
64
+ readonly bold?: boolean;
65
+ readonly italic?: boolean;
66
+ readonly strike?: boolean;
67
+ readonly vertical?: "subscript" | "superscript";
68
+ }
69
+
70
+ interface NumberingReference {
71
+ readonly id: string;
72
+ readonly level: number;
73
+ }
74
+
75
+ interface Style {
76
+ readonly id: string;
77
+ readonly type: "paragraph" | "character";
78
+ readonly name: string;
79
+ readonly basedOn?: string;
80
+ readonly formatting: Formatting;
81
+ readonly numbering?: NumberingReference;
82
+ readonly isDefault: boolean;
83
+ }
84
+
85
+ interface CustomStyle {
86
+ readonly kind: "paragraph" | "character";
87
+ readonly styleName: string;
88
+ readonly tag: string;
89
+ }
90
+
91
+ interface ParagraphBlock {
92
+ readonly kind: "paragraph";
93
+ readonly html: string;
94
+ readonly tag: string;
95
+ readonly list?: { readonly level: number; readonly ordered: boolean };
96
+ }
97
+
98
+ interface RawTableCell {
99
+ readonly html: string;
100
+ readonly columnSpan: number;
101
+ readonly merge: "restart" | "continue" | undefined;
102
+ }
103
+
104
+ interface TableCell {
105
+ readonly html: string;
106
+ readonly columnSpan: number;
107
+ rowSpan: number;
108
+ }
109
+
110
+ interface HtmlBlock {
111
+ readonly kind: "html";
112
+ readonly html: string;
113
+ }
114
+
115
+ type Block = ParagraphBlock | HtmlBlock;
116
+
117
+ interface NumberingLevel {
118
+ readonly ordered: boolean;
119
+ }
120
+
121
+ interface ConversionContext {
122
+ readonly entries: ZipEntries;
123
+ readonly relationships: ReadonlyMap<string, Relationship>;
124
+ readonly contentTypes: ReadonlyMap<string, string>;
125
+ readonly styles: ReadonlyMap<string, Style>;
126
+ readonly numbering: ReadonlyMap<string, ReadonlyMap<number, NumberingLevel>>;
127
+ readonly messages: DocxMessage[];
128
+ readonly warnedStyles: Set<string>;
129
+ readonly customStyles: readonly CustomStyle[];
130
+ readonly includeDefaultStyleMap: boolean;
131
+ readonly convertImage: ImageConverter;
132
+ readonly footnotes: ReadonlyMap<string, XmlElement>;
133
+ readonly usedFootnotes: Array<{ readonly id: string; readonly ordinal: number }>;
134
+ readonly footnoteOrdinals: Map<string, number>;
135
+ }
136
+
137
+ const IMAGE_CONTENT_TYPE_BY_EXTENSION: Readonly<Record<string, string>> = {
138
+ bmp: "image/bmp",
139
+ gif: "image/gif",
140
+ jpeg: "image/jpeg",
141
+ jpg: "image/jpeg",
142
+ png: "image/png",
143
+ svg: "image/svg+xml",
144
+ tif: "image/tiff",
145
+ tiff: "image/tiff",
146
+ webp: "image/webp",
147
+ };
148
+
149
+ function escapeText(value: string): string {
150
+ return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
151
+ }
152
+
153
+ function escapeAttribute(value: string): string {
154
+ return escapeText(value).replaceAll('"', "&quot;");
155
+ }
156
+
157
+ function enabled(property: XmlElement | undefined): boolean | undefined {
158
+ if (!property) return undefined;
159
+ const value = attribute(property, "w:val")?.toLowerCase();
160
+ return value !== "0" && value !== "false" && value !== "off" && value !== "none";
161
+ }
162
+
163
+ function formattingFromProperties(properties: XmlElement | undefined): Formatting {
164
+ if (!properties) return {};
165
+ const vertical = attribute(firstChild(properties, "vertAlign"), "w:val");
166
+ return {
167
+ bold: enabled(firstChild(properties, "b")),
168
+ italic: enabled(firstChild(properties, "i")),
169
+ strike: enabled(firstChild(properties, "strike")) ?? enabled(firstChild(properties, "dstrike")),
170
+ vertical: vertical === "subscript" || vertical === "superscript" ? vertical : undefined,
171
+ };
172
+ }
173
+
174
+ function mergeFormatting(base: Formatting, override: Formatting): Formatting {
175
+ return {
176
+ bold: override.bold ?? base.bold,
177
+ italic: override.italic ?? base.italic,
178
+ strike: override.strike ?? base.strike,
179
+ vertical: override.vertical ?? base.vertical,
180
+ };
181
+ }
182
+
183
+ function parseNumberingReference(properties: XmlElement | undefined): NumberingReference | undefined {
184
+ const numberProperties = firstChild(properties, "numPr");
185
+ const id = attribute(firstChild(numberProperties, "numId"), "w:val");
186
+ if (!id || id === "0") return undefined;
187
+ const rawLevel = attribute(firstChild(numberProperties, "ilvl"), "w:val");
188
+ return { id, level: rawLevel ? Number.parseInt(rawLevel, 10) || 0 : 0 };
189
+ }
190
+
191
+ function parseStyles(xml: string | undefined): Map<string, Style> {
192
+ const styles = new Map<string, Style>();
193
+ if (!xml) return styles;
194
+ const root = parseXml(xml);
195
+ for (const element of childElements(root, "style")) {
196
+ const id = attribute(element, "w:styleId");
197
+ const rawType = attribute(element, "w:type");
198
+ if (!id || (rawType !== "paragraph" && rawType !== "character")) continue;
199
+ styles.set(id, {
200
+ id,
201
+ type: rawType,
202
+ name: attribute(firstChild(element, "name"), "w:val") ?? id,
203
+ basedOn: attribute(firstChild(element, "basedOn"), "w:val"),
204
+ formatting: formattingFromProperties(firstChild(element, "rPr")),
205
+ numbering: parseNumberingReference(firstChild(element, "pPr")),
206
+ isDefault: attribute(element, "w:default") === "1" || attribute(element, "w:default") === "true",
207
+ });
208
+ }
209
+ return styles;
210
+ }
211
+
212
+ function resolveStyleFormatting(
213
+ style: Style | undefined,
214
+ styles: ReadonlyMap<string, Style>,
215
+ visited = new Set<string>(),
216
+ ): Formatting {
217
+ if (!style || visited.has(style.id)) return {};
218
+ visited.add(style.id);
219
+ const base = style.basedOn ? resolveStyleFormatting(styles.get(style.basedOn), styles, visited) : {};
220
+ return mergeFormatting(base, style.formatting);
221
+ }
222
+
223
+ function resolveStyleNumbering(
224
+ style: Style | undefined,
225
+ styles: ReadonlyMap<string, Style>,
226
+ visited = new Set<string>(),
227
+ ): NumberingReference | undefined {
228
+ if (!style || visited.has(style.id)) return undefined;
229
+ visited.add(style.id);
230
+ return (
231
+ style.numbering ?? (style.basedOn ? resolveStyleNumbering(styles.get(style.basedOn), styles, visited) : undefined)
232
+ );
233
+ }
234
+
235
+ function parseRelationships(xml: string | undefined): Map<string, Relationship> {
236
+ const relationships = new Map<string, Relationship>();
237
+ if (!xml) return relationships;
238
+ const root = parseXml(xml);
239
+ for (const element of childElements(root, "Relationship")) {
240
+ const id = attribute(element, "Id");
241
+ const target = attribute(element, "Target");
242
+ if (!id || !target) continue;
243
+ relationships.set(id, {
244
+ target,
245
+ type: attribute(element, "Type") ?? "",
246
+ external: attribute(element, "TargetMode") === "External",
247
+ });
248
+ }
249
+ return relationships;
250
+ }
251
+
252
+ function parseContentTypes(xml: string | undefined): Map<string, string> {
253
+ const types = new Map<string, string>();
254
+ if (!xml) return types;
255
+ const root = parseXml(xml);
256
+ for (const element of childElements(root)) {
257
+ if (localName(element.name) === "Default") {
258
+ const extension = attribute(element, "Extension")?.toLowerCase();
259
+ const contentType = attribute(element, "ContentType");
260
+ if (extension && contentType) types.set(`.${extension}`, contentType);
261
+ } else if (localName(element.name) === "Override") {
262
+ const part = attribute(element, "PartName");
263
+ const contentType = attribute(element, "ContentType");
264
+ if (part && contentType) types.set(part.startsWith("/") ? part.slice(1) : part, contentType);
265
+ }
266
+ }
267
+ return types;
268
+ }
269
+
270
+ function parseNumbering(xml: string | undefined): Map<string, ReadonlyMap<number, NumberingLevel>> {
271
+ const result = new Map<string, ReadonlyMap<number, NumberingLevel>>();
272
+ if (!xml) return result;
273
+ const root = parseXml(xml);
274
+ const abstractLevels = new Map<string, Map<number, NumberingLevel>>();
275
+ for (const abstract of childElements(root, "abstractNum")) {
276
+ const id = attribute(abstract, "w:abstractNumId");
277
+ if (!id) continue;
278
+ const levels = new Map<number, NumberingLevel>();
279
+ for (const level of childElements(abstract, "lvl")) {
280
+ const index = Number.parseInt(attribute(level, "w:ilvl") ?? "0", 10) || 0;
281
+ levels.set(index, { ordered: attribute(firstChild(level, "numFmt"), "w:val") !== "bullet" });
282
+ }
283
+ abstractLevels.set(id, levels);
284
+ }
285
+ for (const number of childElements(root, "num")) {
286
+ const id = attribute(number, "w:numId");
287
+ const abstractId = attribute(firstChild(number, "abstractNumId"), "w:val");
288
+ if (id && abstractId) result.set(id, abstractLevels.get(abstractId) ?? new Map());
289
+ }
290
+ return result;
291
+ }
292
+
293
+ function parseFootnotes(xml: string | undefined): Map<string, XmlElement> {
294
+ const notes = new Map<string, XmlElement>();
295
+ if (!xml) return notes;
296
+ for (const element of childElements(parseXml(xml), "footnote")) {
297
+ const id = attribute(element, "w:id");
298
+ if (id && !id.startsWith("-")) notes.set(id, element);
299
+ }
300
+ return notes;
301
+ }
302
+
303
+ function parseCustomStyles(styleMap: string | readonly string[] | undefined): CustomStyle[] {
304
+ const lines = typeof styleMap === "string" ? styleMap.split("\n") : (styleMap ?? []);
305
+ const mappings: CustomStyle[] = [];
306
+ for (const line of lines) {
307
+ const match = /^\s*(p|r)\s*\[style-name\s*=\s*['"]([^'"]+)['"]\]\s*=>\s*([\w-]+)/i.exec(line);
308
+ if (!match) continue;
309
+ mappings.push({
310
+ kind: match[1].toLowerCase() === "p" ? "paragraph" : "character",
311
+ styleName: match[2],
312
+ tag: match[3],
313
+ });
314
+ }
315
+ return mappings;
316
+ }
317
+
318
+ function styleMapping(
319
+ context: ConversionContext,
320
+ style: Style | undefined,
321
+ kind: "paragraph" | "character",
322
+ ): CustomStyle | undefined {
323
+ if (!style) return undefined;
324
+ return context.customStyles.find(mapping => mapping.kind === kind && mapping.styleName === style.name);
325
+ }
326
+
327
+ function warnUnrecognisedStyle(
328
+ context: ConversionContext,
329
+ styleId: string,
330
+ style: Style | undefined,
331
+ kind: "paragraph" | "run",
332
+ ): void {
333
+ const key = `${kind}:${styleId}`;
334
+ if (context.warnedStyles.has(key)) return;
335
+ context.warnedStyles.add(key);
336
+ const name = style?.name ?? styleId;
337
+ context.messages.push({ type: "warning", message: `Unrecognised ${kind} style: '${name}' (Style ID: ${styleId})` });
338
+ }
339
+
340
+ function paragraphTag(context: ConversionContext, styleId: string | undefined): string {
341
+ if (!styleId) return "p";
342
+ const style = context.styles.get(styleId);
343
+ const custom = styleMapping(context, style, "paragraph");
344
+ if (custom) return custom.tag;
345
+ const heading = context.includeDefaultStyleMap
346
+ ? /^(?:heading\s*|heading)([1-6])$/i.exec(style?.name ?? styleId)
347
+ : null;
348
+ if (heading) return `h${heading[1]}`;
349
+ if (!style?.isDefault && (style?.name.toLowerCase() ?? styleId.toLowerCase()) !== "normal") {
350
+ warnUnrecognisedStyle(context, styleId, style, "paragraph");
351
+ }
352
+ return "p";
353
+ }
354
+
355
+ function relationshipPath(target: string): string {
356
+ if (target.startsWith("/")) return target.slice(1);
357
+ return path.posix.normalize(path.posix.join("word", target));
358
+ }
359
+
360
+ function renderFormatting(value: string, formatting: Formatting): string {
361
+ let html = value;
362
+ if (formatting.strike) html = `<s>${html}</s>`;
363
+ if (formatting.vertical === "subscript") html = `<sub>${html}</sub>`;
364
+ if (formatting.vertical === "superscript") html = `<sup>${html}</sup>`;
365
+ if (formatting.italic) html = `<em>${html}</em>`;
366
+ if (formatting.bold) html = `<strong>${html}</strong>`;
367
+ return html;
368
+ }
369
+
370
+ async function renderImage(element: XmlElement, context: ConversionContext): Promise<string> {
371
+ const blip = descendants(element, "blip")[0];
372
+ const relationshipId = attribute(blip, "r:embed");
373
+ const relationship = relationshipId ? context.relationships.get(relationshipId) : undefined;
374
+ if (!relationship || relationship.external) return "";
375
+ const memberPath = relationshipPath(relationship.target);
376
+ const bytes = context.entries.get(memberPath);
377
+ if (!bytes) {
378
+ context.messages.push({ type: "warning", message: `Could not find image ${memberPath}` });
379
+ return "";
380
+ }
381
+ const documentProperties = descendants(element, "docPr")[0];
382
+ const altText = attribute(documentProperties, "descr") ?? attribute(documentProperties, "title") ?? "";
383
+ const extension = path.posix.extname(memberPath).toLowerCase();
384
+ const contentType =
385
+ context.contentTypes.get(memberPath) ??
386
+ context.contentTypes.get(extension) ??
387
+ IMAGE_CONTENT_TYPE_BY_EXTENSION[extension.slice(1)] ??
388
+ "application/octet-stream";
389
+ const image: DocxImage = {
390
+ contentType,
391
+ altText,
392
+ async read(encoding: "base64"): Promise<string> {
393
+ if (encoding !== "base64") throw new Error(`Unsupported image encoding: ${encoding}`);
394
+ return Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength).toString("base64");
395
+ },
396
+ };
397
+ const converted = await context.convertImage.convert(image);
398
+ const attributes = Object.entries(converted)
399
+ .filter((entry): entry is [string, string] => typeof entry[1] === "string")
400
+ .sort(([left], [right]) => left.localeCompare(right))
401
+ .map(([name, value]) => `${name}="${escapeAttribute(value)}"`)
402
+ .join(" ");
403
+ return attributes ? `<img ${attributes} />` : "<img />";
404
+ }
405
+
406
+ function renderFootnoteReference(element: XmlElement, context: ConversionContext): string {
407
+ const id = attribute(element, "w:id");
408
+ if (!id || !context.footnotes.has(id)) return "";
409
+ let ordinal = context.footnoteOrdinals.get(id);
410
+ if (ordinal === undefined) {
411
+ ordinal = context.usedFootnotes.length + 1;
412
+ context.footnoteOrdinals.set(id, ordinal);
413
+ context.usedFootnotes.push({ id, ordinal });
414
+ }
415
+ return `<sup><a href="#footnote-${escapeAttribute(id)}" id="footnote-ref-${escapeAttribute(id)}">[${ordinal}]</a></sup>`;
416
+ }
417
+
418
+ async function renderInlineChildren(element: XmlElement, context: ConversionContext): Promise<string> {
419
+ let html = "";
420
+ for (const child of element.children) {
421
+ if (child.kind === "text") continue;
422
+ const name = localName(child.name);
423
+ if (name === "r") {
424
+ html += await renderRun(child, context);
425
+ } else if (name === "hyperlink") {
426
+ const contents = await renderInlineChildren(child, context);
427
+ const relationshipId = attribute(child, "r:id");
428
+ const anchor = attribute(child, "w:anchor");
429
+ const target = relationshipId
430
+ ? context.relationships.get(relationshipId)?.target
431
+ : anchor
432
+ ? `#${anchor}`
433
+ : undefined;
434
+ html += target ? `<a href="${escapeAttribute(target)}">${contents}</a>` : contents;
435
+ } else if (name !== "del") {
436
+ html += await renderInlineChildren(child, context);
437
+ }
438
+ }
439
+ return html;
440
+ }
441
+
442
+ async function renderRun(run: XmlElement, context: ConversionContext): Promise<string> {
443
+ const properties = firstChild(run, "rPr");
444
+ const styleId = attribute(firstChild(properties, "rStyle"), "w:val");
445
+ const style = styleId ? context.styles.get(styleId) : undefined;
446
+ const custom = styleMapping(context, style, "character");
447
+ let formatting = mergeFormatting(
448
+ resolveStyleFormatting(style, context.styles),
449
+ formattingFromProperties(properties),
450
+ );
451
+ if (styleId && !custom) {
452
+ if (context.includeDefaultStyleMap && (style?.name ?? styleId).toLowerCase() === "strong") {
453
+ formatting = { ...formatting, bold: true };
454
+ } else {
455
+ warnUnrecognisedStyle(context, styleId, style, "run");
456
+ }
457
+ }
458
+ let value = "";
459
+ for (const child of childElements(run)) {
460
+ const name = localName(child.name);
461
+ if (name === "t" || name === "instrText") {
462
+ for (const node of child.children) if (node.kind === "text") value += escapeText(node.value);
463
+ } else if (name === "tab") {
464
+ value += "\t";
465
+ } else if (name === "br") {
466
+ if (attribute(child, "w:type") !== "page") value += "<br />";
467
+ } else if (name === "noBreakHyphen") {
468
+ value += "‑";
469
+ } else if (name === "softHyphen") {
470
+ value += "­";
471
+ } else if (name === "drawing" || name === "pict") {
472
+ value += await renderImage(child, context);
473
+ } else if (name === "footnoteReference") {
474
+ value += renderFootnoteReference(child, context);
475
+ }
476
+ }
477
+ let html = renderFormatting(value, formatting);
478
+ if (custom) html = `<${custom.tag}>${html}</${custom.tag}>`;
479
+ return html;
480
+ }
481
+
482
+ async function parseParagraph(element: XmlElement, context: ConversionContext): Promise<ParagraphBlock | undefined> {
483
+ const properties = firstChild(element, "pPr");
484
+ const styleId = attribute(firstChild(properties, "pStyle"), "w:val");
485
+ const style = styleId ? context.styles.get(styleId) : undefined;
486
+ const tag = paragraphTag(context, styleId);
487
+ const directNumbering = parseNumberingReference(properties);
488
+ const numberingReference = directNumbering ?? resolveStyleNumbering(style, context.styles);
489
+ const html = await renderInlineChildren(element, context);
490
+ if (!html && !numberingReference) return undefined;
491
+ const numberingLevel = numberingReference
492
+ ? context.numbering.get(numberingReference.id)?.get(numberingReference.level)
493
+ : undefined;
494
+ return {
495
+ kind: "paragraph",
496
+ html,
497
+ tag,
498
+ list: numberingReference
499
+ ? { level: numberingReference.level, ordered: numberingLevel?.ordered ?? true }
500
+ : undefined,
501
+ };
502
+ }
503
+
504
+ function renderList(
505
+ items: readonly ParagraphBlock[],
506
+ start: number,
507
+ level: number,
508
+ ): { readonly html: string; readonly next: number } {
509
+ const ordered = items[start].list?.ordered ?? true;
510
+ const tag = ordered ? "ol" : "ul";
511
+ let html = `<${tag}>`;
512
+ let index = start;
513
+ while (index < items.length) {
514
+ const item = items[index];
515
+ const list = item.list;
516
+ if (!list || list.level < level || (list.level === level && list.ordered !== ordered)) break;
517
+ if (list.level > level) break;
518
+ html += `<li>${item.html}`;
519
+ index++;
520
+ while (index < items.length && (items[index].list?.level ?? -1) > level) {
521
+ const nested = renderList(items, index, items[index].list?.level ?? level + 1);
522
+ html += nested.html;
523
+ index = nested.next;
524
+ }
525
+ html += "</li>";
526
+ }
527
+ html += `</${tag}>`;
528
+ return { html, next: index };
529
+ }
530
+
531
+ function renderBlocks(blocks: readonly Block[]): string {
532
+ let html = "";
533
+ for (let index = 0; index < blocks.length; ) {
534
+ const block = blocks[index];
535
+ if (block.kind === "html") {
536
+ html += block.html;
537
+ index++;
538
+ continue;
539
+ }
540
+ if (!block.list) {
541
+ html += `<${block.tag}>${block.html}</${block.tag}>`;
542
+ index++;
543
+ continue;
544
+ }
545
+ const listItems: ParagraphBlock[] = [];
546
+ while (index < blocks.length) {
547
+ const candidate = blocks[index];
548
+ if (candidate.kind !== "paragraph" || !candidate.list) break;
549
+ listItems.push(candidate);
550
+ index++;
551
+ }
552
+ for (let itemIndex = 0; itemIndex < listItems.length; ) {
553
+ const rendered = renderList(listItems, itemIndex, listItems[itemIndex].list?.level ?? 0);
554
+ html += rendered.html;
555
+ itemIndex = rendered.next;
556
+ }
557
+ }
558
+ return html;
559
+ }
560
+
561
+ async function parseTableCell(element: XmlElement, context: ConversionContext): Promise<RawTableCell> {
562
+ const properties = firstChild(element, "tcPr");
563
+ const span = Number.parseInt(attribute(firstChild(properties, "gridSpan"), "w:val") ?? "1", 10) || 1;
564
+ const verticalMerge = firstChild(properties, "vMerge");
565
+ const mergeValue = attribute(verticalMerge, "w:val");
566
+ const merge = verticalMerge ? (mergeValue === "restart" ? "restart" : "continue") : undefined;
567
+ const blocks = await parseBlocks(childElements(element), context);
568
+ return { html: renderBlocks(blocks), columnSpan: span, merge };
569
+ }
570
+
571
+ async function renderTable(element: XmlElement, context: ConversionContext): Promise<string> {
572
+ const rows: TableCell[][] = [];
573
+ let active = new Map<number, TableCell>();
574
+ for (const rowElement of childElements(element, "tr")) {
575
+ const row: TableCell[] = [];
576
+ const nextActive = new Map<number, TableCell>();
577
+ let column = 0;
578
+ const extended = new Set<TableCell>();
579
+ for (const cellElement of childElements(rowElement, "tc")) {
580
+ const raw = await parseTableCell(cellElement, context);
581
+ if (raw.merge === "continue") {
582
+ const origin = active.get(column);
583
+ if (origin) {
584
+ if (!extended.has(origin)) {
585
+ origin.rowSpan++;
586
+ extended.add(origin);
587
+ }
588
+ for (let offset = 0; offset < raw.columnSpan; offset++) nextActive.set(column + offset, origin);
589
+ column += raw.columnSpan;
590
+ continue;
591
+ }
592
+ }
593
+ const cell: TableCell = { html: raw.html, columnSpan: raw.columnSpan, rowSpan: 1 };
594
+ row.push(cell);
595
+ if (raw.merge === "restart") {
596
+ for (let offset = 0; offset < raw.columnSpan; offset++) nextActive.set(column + offset, cell);
597
+ }
598
+ column += raw.columnSpan;
599
+ }
600
+ rows.push(row);
601
+ active = nextActive;
602
+ }
603
+ let html = "<table>";
604
+ for (const row of rows) {
605
+ html += "<tr>";
606
+ for (const cell of row) {
607
+ const columnSpan = cell.columnSpan > 1 ? ` colspan="${cell.columnSpan}"` : "";
608
+ const rowSpan = cell.rowSpan > 1 ? ` rowspan="${cell.rowSpan}"` : "";
609
+ html += `<td${columnSpan}${rowSpan}>${cell.html}</td>`;
610
+ }
611
+ html += "</tr>";
612
+ }
613
+ return `${html}</table>`;
614
+ }
615
+
616
+ async function parseBlocks(elements: readonly XmlElement[], context: ConversionContext): Promise<Block[]> {
617
+ const blocks: Block[] = [];
618
+ for (const element of elements) {
619
+ const name = localName(element.name);
620
+ if (name === "p") {
621
+ const paragraph = await parseParagraph(element, context);
622
+ if (paragraph) blocks.push(paragraph);
623
+ } else if (name === "tbl") {
624
+ blocks.push({ kind: "html", html: await renderTable(element, context) });
625
+ } else if (name === "sdt" || name === "customXml") {
626
+ blocks.push(...(await parseBlocks(childElements(element), context)));
627
+ }
628
+ }
629
+ return blocks;
630
+ }
631
+
632
+ async function renderFootnotes(context: ConversionContext): Promise<string> {
633
+ if (context.usedFootnotes.length === 0) return "";
634
+ let html = "<ol>";
635
+ for (const note of context.usedFootnotes) {
636
+ const element = context.footnotes.get(note.id);
637
+ if (!element) continue;
638
+ let contents = renderBlocks(await parseBlocks(childElements(element), context));
639
+ const backlink = `<a href="#footnote-ref-${escapeAttribute(note.id)}">↑</a>`;
640
+ const lastParagraph = contents.lastIndexOf("</p>");
641
+ if (lastParagraph === -1) contents += backlink;
642
+ else contents = `${contents.slice(0, lastParagraph)} ${backlink}${contents.slice(lastParagraph)}`;
643
+ html += `<li id="footnote-${escapeAttribute(note.id)}">${contents}</li>`;
644
+ }
645
+ return `${html}</ol>`;
646
+ }
647
+
648
+ function defaultImageConverter(): ImageConverter {
649
+ return images.imgElement(async image => ({
650
+ alt: image.altText,
651
+ src: `data:${image.contentType};base64,${await image.read("base64")}`,
652
+ }));
653
+ }
654
+
655
+ /** Convert a DOCX buffer or path to mammoth-compatible HTML. */
656
+ export async function convertToHtml(input: DocxInput, options: ConvertToHtmlOptions = {}): Promise<DocxResult> {
657
+ const bytes = "buffer" in input && input.buffer ? input.buffer : await fs.readFile(input.path);
658
+ const entries = readZip(bytes);
659
+ const documentXml = readZipText(entries, "word/document.xml");
660
+ if (!documentXml) throw new Error("Invalid DOCX: missing word/document.xml");
661
+ const context: ConversionContext = {
662
+ entries,
663
+ relationships: parseRelationships(readZipText(entries, "word/_rels/document.xml.rels")),
664
+ contentTypes: parseContentTypes(readZipText(entries, "[Content_Types].xml")),
665
+ styles: parseStyles(readZipText(entries, "word/styles.xml")),
666
+ numbering: parseNumbering(readZipText(entries, "word/numbering.xml")),
667
+ messages: [],
668
+ warnedStyles: new Set(),
669
+ customStyles: parseCustomStyles(options.styleMap),
670
+ includeDefaultStyleMap: options.includeDefaultStyleMap !== false,
671
+ convertImage: options.convertImage ?? defaultImageConverter(),
672
+ footnotes: parseFootnotes(readZipText(entries, "word/footnotes.xml")),
673
+ usedFootnotes: [],
674
+ footnoteOrdinals: new Map(),
675
+ };
676
+ const document = parseXml(documentXml);
677
+ const body = firstChild(document, "body");
678
+ if (!body) throw new Error("Invalid DOCX: missing document body");
679
+ const value = renderBlocks(await parseBlocks(childElements(body), context)) + (await renderFootnotes(context));
680
+ return { value, messages: context.messages };
681
+ }