w-md2docx 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 (69) hide show
  1. package/.editorconfig +9 -0
  2. package/.eslintignore +3 -0
  3. package/.eslintrc.js +55 -0
  4. package/.jsdoc +25 -0
  5. package/LICENSE +21 -0
  6. package/README.md +83 -0
  7. package/SECURITY.md +5 -0
  8. package/babel.config.js +16 -0
  9. package/dist/w-md2docx.umd.js +7 -0
  10. package/dist/w-md2docx.umd.js.map +1 -0
  11. package/docs/ApiClient.mjs.html +539 -0
  12. package/docs/ApiServer.mjs.html +516 -0
  13. package/docs/WMd2docx.mjs.html +101 -0
  14. package/docs/cvMdTo.mjs.html +288 -0
  15. package/docs/cvMdToDocx.mjs.html +243 -0
  16. package/docs/fonts/Montserrat/Montserrat-Bold.eot +0 -0
  17. package/docs/fonts/Montserrat/Montserrat-Bold.ttf +0 -0
  18. package/docs/fonts/Montserrat/Montserrat-Bold.woff +0 -0
  19. package/docs/fonts/Montserrat/Montserrat-Bold.woff2 +0 -0
  20. package/docs/fonts/Montserrat/Montserrat-Regular.eot +0 -0
  21. package/docs/fonts/Montserrat/Montserrat-Regular.ttf +0 -0
  22. package/docs/fonts/Montserrat/Montserrat-Regular.woff +0 -0
  23. package/docs/fonts/Montserrat/Montserrat-Regular.woff2 +0 -0
  24. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.eot +0 -0
  25. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.svg +978 -0
  26. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.ttf +0 -0
  27. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff +0 -0
  28. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff2 +0 -0
  29. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.eot +0 -0
  30. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.svg +1049 -0
  31. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.ttf +0 -0
  32. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff +0 -0
  33. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff2 +0 -0
  34. package/docs/global.html +5453 -0
  35. package/docs/index.html +84 -0
  36. package/docs/scripts/collapse.js +39 -0
  37. package/docs/scripts/commonNav.js +28 -0
  38. package/docs/scripts/linenumber.js +25 -0
  39. package/docs/scripts/nav.js +12 -0
  40. package/docs/scripts/polyfill.js +4 -0
  41. package/docs/scripts/prettify/Apache-License-2.0.txt +202 -0
  42. package/docs/scripts/prettify/lang-css.js +2 -0
  43. package/docs/scripts/prettify/prettify.js +28 -0
  44. package/docs/scripts/search.js +99 -0
  45. package/docs/styles/jsdoc.css +776 -0
  46. package/docs/styles/prettify.css +80 -0
  47. package/docs/utils.mjs.html +520 -0
  48. package/g.mjs +34 -0
  49. package/package.json +32 -0
  50. package/script.txt +18 -0
  51. package/src/ApiClient.mjs +467 -0
  52. package/src/ApiServer.mjs +444 -0
  53. package/src/WMd2docx.mjs +29 -0
  54. package/src/cvMdTo.mjs +216 -0
  55. package/src/cvMdToDocx.mjs +171 -0
  56. package/src/templates/temp_tpc.docx +0 -0
  57. package/src/utils.mjs +448 -0
  58. package/test/api-ApiClient.test.mjs +221 -0
  59. package/test/api-ApiServer.test.mjs +191 -0
  60. package/test/cocktail.svg +3 -0
  61. package/test/report.md +257 -0
  62. package/test/unit-cvMdTo.test.mjs +132 -0
  63. package/test/unit-cvMdToDocx.test.mjs +185 -0
  64. package/test/unit-utils.test.mjs +370 -0
  65. package/toolg/addVersion.mjs +4 -0
  66. package/toolg/cleanFolder.mjs +5 -0
  67. package/toolg/gDistRollup.mjs +56 -0
  68. package/toolg/gDocsExams.mjs +51 -0
  69. package/toolg/modifyReadme.mjs +4 -0
@@ -0,0 +1,288 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+
5
+ <meta charset="utf-8">
6
+ <title>cvMdTo.mjs - Documentation</title>
7
+
8
+
9
+ <script src="scripts/prettify/prettify.js"></script>
10
+ <script src="scripts/prettify/lang-css.js"></script>
11
+ <!--[if lt IE 9]>
12
+ <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
13
+ <![endif]-->
14
+ <link type="text/css" rel="stylesheet" href="styles/prettify.css">
15
+ <link type="text/css" rel="stylesheet" href="styles/jsdoc.css">
16
+ <script src="scripts/nav.js" defer></script>
17
+
18
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
19
+ </head>
20
+ <body>
21
+
22
+ <input type="checkbox" id="nav-trigger" class="nav-trigger" />
23
+ <label for="nav-trigger" class="navicon-button x">
24
+ <div class="navicon"></div>
25
+ </label>
26
+
27
+ <label for="nav-trigger" class="overlay"></label>
28
+
29
+ <nav >
30
+
31
+
32
+ <h2><a href="index.html">Home</a></h2><h3>Global</h3><ul><li><a href="global.html#ApiServer">ApiServer</a></li><li><a href="global.html#WMd2docx">WMd2docx</a></li><li><a href="global.html#checkDocxReady">checkDocxReady</a></li><li><a href="global.html#cleanWorkDir">cleanWorkDir</a></li><li><a href="global.html#cvMdTo">cvMdTo</a></li><li><a href="global.html#cvMdToDocx">cvMdToDocx</a></li><li><a href="global.html#getFpExe">getFpExe</a></li><li><a href="global.html#getQueueSize">getQueueSize</a></li><li><a href="global.html#health">health</a></li><li><a href="global.html#readAssets">readAssets</a></li><li><a href="global.html#resolveAssetPaths">resolveAssetPaths</a></li><li><a href="global.html#retryBusy">retryBusy</a></li><li><a href="global.html#runExclusive">runExclusive</a></li><li><a href="global.html#scanAssetPaths">scanAssetPaths</a></li><li><a href="global.html#toErrText">toErrText</a></li><li><a href="global.html#toSafeName">toSafeName</a></li><li><a href="global.html#transErrAsset">transErrAsset</a></li><li><a href="global.html#writeAssets">writeAssets</a></li></ul>
33
+
34
+ </nav>
35
+
36
+ <div id="main">
37
+
38
+ <h1 class="page-title">cvMdTo.mjs</h1>
39
+
40
+
41
+
42
+
43
+
44
+
45
+
46
+ <section>
47
+ <article>
48
+ <pre class="prettyprint source linenums"><code>import fs from 'fs'
49
+ import os from 'os'
50
+ import path from 'path'
51
+ import get from 'lodash-es/get.js'
52
+ import isestr from 'wsemi/src/isestr.mjs'
53
+ import isbol from 'wsemi/src/isbol.mjs'
54
+ import isobj from 'wsemi/src/isobj.mjs'
55
+ import isarr from 'wsemi/src/isarr.mjs'
56
+ import fsIsFile from 'wsemi/src/fsIsFile.mjs'
57
+ import WMd2html from 'w-md2html/src/WMd2html.mjs'
58
+ import cvMdToDocx from './cvMdToDocx.mjs'
59
+ import { mimeHtml, mimeDocx, toErrText, retryBusy, toSafeName, writeAssets, transErrAsset } from './utils.mjs'
60
+
61
+
62
+ /**
63
+ * Markdown內容轉Html與Docx內容
64
+ *
65
+ * 與cvMdToDocx之差異:本函數接收Markdown「內容」與其引用之資產(圖片等),於獨立工作資料夾內還原後轉檔,完成後回傳產物之base64內容並清除工作資料夾。適用於服務端等無實體來源檔之情境;有實體md檔者請直接用cvMdToDocx。
66
+ *
67
+ * @param {Object} [opt={}] 輸入設定物件,預設{}
68
+ * @param {String} [opt.md=''] 輸入Markdown內容字串,與mdBase64二擇一
69
+ * @param {String} [opt.mdBase64=''] 輸入Markdown內容之base64字串,與md二擇一
70
+ * @param {String} [opt.name='output'] 輸入輸出檔名主體字串(不含副檔名),將自動移除路徑成分與非法字元,預設'output'
71
+ * @param {String} [opt.out='docx'] 輸入產出類型字串,可為'html'、'docx'、'both',預設'docx'
72
+ * @param {Array} [opt.assets=[]] 輸入md內引用之相對路徑資產陣列,格式[{path,base64}],預設[]
73
+ * @param {String} [opt.dirWork=''] 輸入工作資料夾根位置字串,未給則用系統暫存夾下之w-md2docx,預設''
74
+ * @param {String} [opt.fpInTemp=''] 輸入Docx模板檔位置字串,未給則由w-html2docx使用其內建模板,預設''
75
+ * @param {Object} [opt.optMd2html={}] 輸入傳予w-md2html之設定物件,預設{}
76
+ * @param {Object} [opt.optHtml2docx={}] 輸入傳予w-html2docx之設定物件,預設{}
77
+ * @param {Boolean} [opt.keepWork=false] 輸入是否保留工作資料夾供除錯布林值,預設false
78
+ * @returns {Promise} 回傳Promise,resolve回傳結果物件{name,out,ms,nAssets,[msDocx],[html],[docx]},其中html與docx為{fileName,mime,size,base64},reject回傳錯誤訊息
79
+ * @example
80
+ *
81
+ * import cvMdTo from 'w-md2docx/src/cvMdTo.mjs'
82
+ *
83
+ * let r = await cvMdTo({
84
+ * md: '# 標題\n\n&lt;img src="pics/圖.png" />',
85
+ * name: '報告R00.01',
86
+ * out: 'docx',
87
+ * assets: [{ path: 'pics/圖.png', base64: '…' }],
88
+ * })
89
+ * console.log(r)
90
+ * // => { name: '報告R00.01', out: 'docx', ms: 8342, nAssets: 1, msDocx: 8300, docx: { fileName: '報告R00.01.docx', mime: '…', size: 39856, base64: '…' } }
91
+ *
92
+ */
93
+ async function cvMdTo(opt = {}) {
94
+
95
+ let msStart = Date.now()
96
+
97
+ //out
98
+ let out = get(opt, 'out', '')
99
+ if (out !== 'html' &amp;&amp; out !== 'docx' &amp;&amp; out !== 'both') {
100
+ out = 'docx'
101
+ }
102
+ let needHtml = (out === 'html' || out === 'both')
103
+ let needDocx = (out === 'docx' || out === 'both')
104
+
105
+ //md
106
+ let md = ''
107
+ let mdIn = get(opt, 'md', '')
108
+ let mdBase64 = get(opt, 'mdBase64', '')
109
+ if (isestr(mdIn)) {
110
+ md = mdIn
111
+ }
112
+ else if (isestr(mdBase64)) {
113
+ md = Buffer.from(mdBase64, 'base64').toString('utf8')
114
+ }
115
+ if (md.trim() === '') {
116
+ return Promise.reject('md is empty: md (string) or mdBase64 is required')
117
+ }
118
+
119
+ //name
120
+ let name = toSafeName(get(opt, 'name', ''))
121
+
122
+ //assets
123
+ let assets = get(opt, 'assets', [])
124
+ if (!isarr(assets)) {
125
+ assets = []
126
+ }
127
+
128
+ //dirWork (未指定則用系統暫存夾)
129
+ let dirWork = get(opt, 'dirWork', '')
130
+ if (!isestr(dirWork)) {
131
+ dirWork = path.join(os.tmpdir(), 'w-md2docx')
132
+ }
133
+ dirWork = path.resolve(dirWork)
134
+
135
+ //fpInTemp (存在性由 cvMdToDocx 檢查)
136
+ let fpInTemp = get(opt, 'fpInTemp', '')
137
+ if (!isestr(fpInTemp)) {
138
+ fpInTemp = ''
139
+ }
140
+
141
+ //optMd2html
142
+ let optMd2html = get(opt, 'optMd2html', {})
143
+ if (!isobj(optMd2html)) {
144
+ optMd2html = {}
145
+ }
146
+
147
+ //optHtml2docx
148
+ let optHtml2docx = get(opt, 'optHtml2docx', {})
149
+ if (!isobj(optHtml2docx)) {
150
+ optHtml2docx = {}
151
+ }
152
+
153
+ //keepWork (除錯用, 保留工作資料夾)
154
+ let keepWork = get(opt, 'keepWork', false)
155
+ if (!isbol(keepWork)) {
156
+ keepWork = false
157
+ }
158
+
159
+ //dirJob (每次作業獨立資料夾, 使同名檔案與資產互不干擾)
160
+ let idJob = `${Date.now()}_${Math.random().toString(36).slice(2, 8)}`
161
+ let dirJob = path.resolve(dirWork, `job_${idJob}`)
162
+ fs.mkdirSync(dirJob, { recursive: true })
163
+
164
+ let rt = {
165
+ name,
166
+ out,
167
+ ms: 0,
168
+ nAssets: 0,
169
+ }
170
+
171
+ try {
172
+
173
+ //寫入 md (資產以 md 所在資料夾為基準解析, 故兩者同置於 dirJob)
174
+ let fpMd = path.resolve(dirJob, `${name}.md`)
175
+ fs.writeFileSync(fpMd, md, 'utf8')
176
+
177
+ //寫入資產
178
+ try {
179
+ rt.nAssets = writeAssets(dirJob, assets)
180
+ }
181
+ catch (err) {
182
+ return Promise.reject(toErrText(err))
183
+ }
184
+
185
+ let fpHtml = path.resolve(dirJob, `${name}.html`)
186
+
187
+ if (needDocx) {
188
+
189
+ //md -> html -> docx (中介 html 指定產於作業夾, 供 out 含 html 時一併回傳)
190
+ let fpDocx = path.resolve(dirJob, `${name}.docx`)
191
+ let errDocx = null
192
+ let r = await cvMdToDocx(fpMd, fpDocx, {
193
+ fpInTemp,
194
+ fpOutHtml: fpHtml,
195
+ optMd2html,
196
+ optHtml2docx,
197
+ })
198
+ .catch((err) => {
199
+ errDocx = transErrAsset(toErrText(err))
200
+ })
201
+ if (errDocx !== null) {
202
+ return Promise.reject(errDocx)
203
+ }
204
+
205
+ let buf = fs.readFileSync(fpDocx)
206
+ rt.docx = {
207
+ fileName: `${name}.docx`,
208
+ mime: mimeDocx,
209
+ size: buf.length,
210
+ base64: buf.toString('base64'),
211
+ }
212
+ rt.msDocx = get(r, 'ms', 0)
213
+
214
+ }
215
+ else {
216
+
217
+ //僅需 html, 不啟動 Word
218
+ let errHtml = null
219
+ await retryBusy(() => WMd2html(fpMd, fpHtml, optMd2html))
220
+ .catch((err) => {
221
+ errHtml = toErrText(err)
222
+ })
223
+ if (errHtml !== null) {
224
+ return Promise.reject(transErrAsset(`Failed to convert md to html: ${errHtml}`))
225
+ }
226
+ if (!fsIsFile(fpHtml) || fs.statSync(fpHtml).size === 0) {
227
+ return Promise.reject('Failed to convert md to html: html was not generated or is empty')
228
+ }
229
+
230
+ }
231
+
232
+ if (needHtml) {
233
+ let buf = fs.readFileSync(fpHtml)
234
+ rt.html = {
235
+ fileName: `${name}.html`,
236
+ mime: mimeHtml,
237
+ size: buf.length,
238
+ base64: buf.toString('base64'),
239
+ }
240
+ }
241
+
242
+ rt.ms = Date.now() - msStart
243
+ return rt
244
+
245
+ }
246
+ finally {
247
+
248
+ //清除作業資料夾(除錯模式保留)
249
+ if (!keepWork) {
250
+ try {
251
+ fs.rmSync(dirJob, { recursive: true, force: true })
252
+ }
253
+ catch (err) {
254
+ //殘檔被鎖, 留待下次清理
255
+ }
256
+ }
257
+
258
+ }
259
+
260
+ }
261
+
262
+
263
+ export default cvMdTo
264
+ </code></pre>
265
+ </article>
266
+ </section>
267
+
268
+
269
+
270
+
271
+
272
+
273
+ </div>
274
+
275
+ <br class="clear">
276
+
277
+ <footer>
278
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Tue Sep 01 2026 16:40:03 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
279
+ </footer>
280
+
281
+ <script>prettyPrint();</script>
282
+ <script src="scripts/polyfill.js"></script>
283
+ <script src="scripts/linenumber.js"></script>
284
+
285
+
286
+
287
+ </body>
288
+ </html>
@@ -0,0 +1,243 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+
5
+ <meta charset="utf-8">
6
+ <title>cvMdToDocx.mjs - Documentation</title>
7
+
8
+
9
+ <script src="scripts/prettify/prettify.js"></script>
10
+ <script src="scripts/prettify/lang-css.js"></script>
11
+ <!--[if lt IE 9]>
12
+ <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
13
+ <![endif]-->
14
+ <link type="text/css" rel="stylesheet" href="styles/prettify.css">
15
+ <link type="text/css" rel="stylesheet" href="styles/jsdoc.css">
16
+ <script src="scripts/nav.js" defer></script>
17
+
18
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
19
+ </head>
20
+ <body>
21
+
22
+ <input type="checkbox" id="nav-trigger" class="nav-trigger" />
23
+ <label for="nav-trigger" class="navicon-button x">
24
+ <div class="navicon"></div>
25
+ </label>
26
+
27
+ <label for="nav-trigger" class="overlay"></label>
28
+
29
+ <nav >
30
+
31
+
32
+ <h2><a href="index.html">Home</a></h2><h3>Global</h3><ul><li><a href="global.html#ApiServer">ApiServer</a></li><li><a href="global.html#WMd2docx">WMd2docx</a></li><li><a href="global.html#checkDocxReady">checkDocxReady</a></li><li><a href="global.html#cleanWorkDir">cleanWorkDir</a></li><li><a href="global.html#cvMdTo">cvMdTo</a></li><li><a href="global.html#cvMdToDocx">cvMdToDocx</a></li><li><a href="global.html#getFpExe">getFpExe</a></li><li><a href="global.html#getQueueSize">getQueueSize</a></li><li><a href="global.html#health">health</a></li><li><a href="global.html#readAssets">readAssets</a></li><li><a href="global.html#resolveAssetPaths">resolveAssetPaths</a></li><li><a href="global.html#retryBusy">retryBusy</a></li><li><a href="global.html#runExclusive">runExclusive</a></li><li><a href="global.html#scanAssetPaths">scanAssetPaths</a></li><li><a href="global.html#toErrText">toErrText</a></li><li><a href="global.html#toSafeName">toSafeName</a></li><li><a href="global.html#transErrAsset">transErrAsset</a></li><li><a href="global.html#writeAssets">writeAssets</a></li></ul>
33
+
34
+ </nav>
35
+
36
+ <div id="main">
37
+
38
+ <h1 class="page-title">cvMdToDocx.mjs</h1>
39
+
40
+
41
+
42
+
43
+
44
+
45
+
46
+ <section>
47
+ <article>
48
+ <pre class="prettyprint source linenums"><code>import fs from 'fs'
49
+ import os from 'os'
50
+ import path from 'path'
51
+ import get from 'lodash-es/get.js'
52
+ import isestr from 'wsemi/src/isestr.mjs'
53
+ import isobj from 'wsemi/src/isobj.mjs'
54
+ import fsIsFile from 'wsemi/src/fsIsFile.mjs'
55
+ import WMd2html from 'w-md2html/src/WMd2html.mjs'
56
+ import WHtml2docx from 'w-html2docx/src/WHtml2docx.mjs'
57
+ import { toErrText, retryBusy, runExclusive, getFpExe } from './utils.mjs'
58
+
59
+
60
+ /**
61
+ * Markdown檔轉Docx檔
62
+ *
63
+ * 內部流程為md -> html -> docx。docx階段係調用本機Microsoft Word(win32com),故僅能於已安裝Word之Windows執行,且同時間僅一份轉檔作業(內部佇列自動排隊)。
64
+ *
65
+ * 注意:底層w-html2docx於Word未安裝或COM呼叫失敗時「仍回傳ok」,本函數一律以產物實體檔之存在與大小驗證成敗,失敗即reject,不會回報假成功。
66
+ *
67
+ * @param {String} fpInMd 輸入來源Markdown檔位置字串
68
+ * @param {String} fpOutDocx 輸入轉出Docx檔位置字串
69
+ * @param {Object} [opt={}] 輸入設定物件,預設{}
70
+ * @param {String} [opt.fpInTemp=''] 輸入Docx模板檔位置字串,未給則由w-html2docx使用其內建模板,預設''
71
+ * @param {String} [opt.fpOutHtml=''] 輸入另存中介Html檔位置字串,未給則中介檔產於系統暫存夾並於轉檔後刪除,預設''
72
+ * @param {Object} [opt.optMd2html={}] 輸入傳予w-md2html之設定物件(如imgWidthMax、fontSizeScale等),預設{}
73
+ * @param {Object} [opt.optHtml2docx={}] 輸入傳予w-html2docx之設定物件(如imgRatioWidthMax、fontFamilies等),預設{}
74
+ * @returns {Promise} 回傳Promise,resolve回傳結果物件{fpOutDocx,sizeDocx,sizeHtml,ms,[fpOutHtml]},reject回傳錯誤訊息
75
+ * @example
76
+ *
77
+ * import cvMdToDocx from 'w-md2docx/src/cvMdToDocx.mjs'
78
+ *
79
+ * let r = await cvMdToDocx('./test/report.md', './test/report.docx', {
80
+ * fpInTemp: './src/templates/temp_tpc.docx',
81
+ * optMd2html: {
82
+ * imgWidthMax: '500px',
83
+ * },
84
+ * })
85
+ * console.log(r)
86
+ * // => { fpOutDocx: '…', sizeDocx: 39856, sizeHtml: 12345, ms: 8342 }
87
+ *
88
+ */
89
+ async function cvMdToDocx(fpInMd, fpOutDocx, opt = {}) {
90
+
91
+ let msStart = Date.now()
92
+
93
+ //check fpInMd
94
+ if (!isestr(fpInMd)) {
95
+ return Promise.reject('fpInMd must be a non-empty string')
96
+ }
97
+ fpInMd = path.resolve(fpInMd)
98
+ if (!fsIsFile(fpInMd)) {
99
+ return Promise.reject(`fpInMd[${fpInMd}] does not exist`)
100
+ }
101
+
102
+ //check fpOutDocx
103
+ if (!isestr(fpOutDocx)) {
104
+ return Promise.reject('fpOutDocx must be a non-empty string')
105
+ }
106
+ fpOutDocx = path.resolve(fpOutDocx)
107
+
108
+ //fpOutHtml (未指定則產於系統暫存夾, 轉檔後刪除)
109
+ let fpOutHtml = get(opt, 'fpOutHtml', '')
110
+ let bKeepHtml = isestr(fpOutHtml)
111
+ if (bKeepHtml) {
112
+ fpOutHtml = path.resolve(fpOutHtml)
113
+ }
114
+ else {
115
+ //檔名唯一, 避免與被鎖定之同名殘檔衝突造成 EBUSY
116
+ fpOutHtml = path.join(os.tmpdir(), `wmd2docx_${process.pid}_${Date.now()}_${Math.random().toString(36).slice(2, 8)}.html`)
117
+ }
118
+
119
+ //optMd2html
120
+ let optMd2html = get(opt, 'optMd2html', {})
121
+ if (!isobj(optMd2html)) {
122
+ optMd2html = {}
123
+ }
124
+
125
+ //optHtml2docx (複製一份, 避免改動呼叫端物件)
126
+ let optHtml2docx = get(opt, 'optHtml2docx', {})
127
+ if (!isobj(optHtml2docx)) {
128
+ optHtml2docx = {}
129
+ }
130
+ optHtml2docx = { ...optHtml2docx }
131
+
132
+ //fpInTemp (未給則交由 w-html2docx 使用內建模板)
133
+ let fpInTemp = get(opt, 'fpInTemp', '')
134
+ if (isestr(fpInTemp)) {
135
+ fpInTemp = path.resolve(fpInTemp)
136
+ if (!fsIsFile(fpInTemp)) {
137
+ return Promise.reject(`fpInTemp[${fpInTemp}] does not exist`)
138
+ }
139
+ optHtml2docx.fpInTemp = fpInTemp
140
+ }
141
+
142
+ try {
143
+
144
+ //md -> html
145
+ //note: w-md2html 係以 md 所在資料夾解析其內引用之相對路徑圖片, 並預設轉為 base64 內嵌,
146
+ // 故中介 html 置於他處亦不影響圖片
147
+ fs.mkdirSync(path.dirname(fpOutHtml), { recursive: true })
148
+ let errHtml = null
149
+ await retryBusy(() => WMd2html(fpInMd, fpOutHtml, optMd2html))
150
+ .catch((err) => {
151
+ errHtml = toErrText(err)
152
+ })
153
+ if (errHtml !== null) {
154
+ return Promise.reject(`Failed to convert md to html: ${errHtml}`)
155
+ }
156
+
157
+ //驗證 html(產物實體檔為準, 不憑回傳值)
158
+ if (!fsIsFile(fpOutHtml) || fs.statSync(fpOutHtml).size === 0) {
159
+ return Promise.reject('Failed to convert md to html: html was not generated or is empty')
160
+ }
161
+ let sizeHtml = fs.statSync(fpOutHtml).size
162
+
163
+ //事前檢查轉檔器(依 cwd 定位, 查無則此 cwd 下必定失敗, 先行明確報錯)
164
+ let fpExe = getFpExe()
165
+ if (fpExe === '') {
166
+ return Promise.reject(`htmlToDocx.exe not found (cwd=${path.resolve()}), please run from the folder that contains node_modules/w-html2docx`)
167
+ }
168
+
169
+ //html -> docx(排隊逐一執行, 避免同時調用本機 Word)
170
+ fs.mkdirSync(path.dirname(fpOutDocx), { recursive: true })
171
+ let errDocx = null
172
+ await runExclusive(() => retryBusy(() => WHtml2docx(fpOutHtml, fpOutDocx, optHtml2docx)))
173
+ .catch((err) => {
174
+ errDocx = toErrText(err)
175
+ })
176
+ if (errDocx !== null) {
177
+ return Promise.reject(`Failed to convert html to docx: ${errDocx}`)
178
+ }
179
+
180
+ //驗證 docx
181
+ //why: w-html2docx 於 Word 未安裝、COM 呼叫失敗等情形「仍回傳 ok」(錯誤僅印於其子程序之輸出),
182
+ // 故一律以產物實體檔之存在與大小為準, 否則將回報假成功
183
+ if (!fsIsFile(fpOutDocx) || fs.statSync(fpOutDocx).size === 0) {
184
+ return Promise.reject('docx was not generated: please make sure Microsoft Word is installed and no stale WINWORD process is locking the files')
185
+ }
186
+ let sizeDocx = fs.statSync(fpOutDocx).size
187
+
188
+ let rt = {
189
+ fpOutDocx,
190
+ sizeDocx,
191
+ sizeHtml,
192
+ ms: Date.now() - msStart,
193
+ }
194
+ if (bKeepHtml) {
195
+ rt.fpOutHtml = fpOutHtml
196
+ }
197
+
198
+ return rt
199
+
200
+ }
201
+ finally {
202
+
203
+ //刪除中介 html(使用者指定保留者不刪; 被鎖就略過, 留待系統暫存清理)
204
+ if (!bKeepHtml &amp;&amp; fsIsFile(fpOutHtml)) {
205
+ try {
206
+ fs.unlinkSync(fpOutHtml)
207
+ }
208
+ catch (err) {
209
+ //殘檔被鎖, 略過
210
+ }
211
+ }
212
+
213
+ }
214
+
215
+ }
216
+
217
+
218
+ export default cvMdToDocx
219
+ </code></pre>
220
+ </article>
221
+ </section>
222
+
223
+
224
+
225
+
226
+
227
+
228
+ </div>
229
+
230
+ <br class="clear">
231
+
232
+ <footer>
233
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Tue Sep 01 2026 16:40:03 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
234
+ </footer>
235
+
236
+ <script>prettyPrint();</script>
237
+ <script src="scripts/polyfill.js"></script>
238
+ <script src="scripts/linenumber.js"></script>
239
+
240
+
241
+
242
+ </body>
243
+ </html>