w-html2docx 1.0.28 → 1.0.29

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.
@@ -1,29 +1,25 @@
1
- import path from 'path'
2
- import { fileURLToPath } from 'url'
3
- import downloadFiles from '../src/downloadFiles.mjs'
4
-
5
-
6
- async function init() {
7
-
8
- //check
9
- let __dirname = path.dirname(fileURLToPath(import.meta.url))
10
- if (!__dirname.includes('node_modules')) {
11
- return //非位於node_modules, 代表套件本身
12
- }
13
-
14
- //fdSrv, postinstall時cwd=套件自身在node_modules內的目錄
15
- let fdSrv = path.resolve()
16
-
17
- //fdBase, npm i後觸發安裝時, 工作路徑是位於套件/專案內
18
- let fdBase = `${fdSrv}/src/`
19
-
20
- //downloadFiles
21
- await downloadFiles(fdBase)
22
-
23
- }
24
- init()
25
- .catch((err) => {
26
- console.log(err)
27
- })
28
-
29
- //node scripts/install.mjs
1
+ import path from 'path'
2
+ import { fileURLToPath } from 'url'
3
+ import autoDownloadFiles from '../src/autoDownloadFiles.mjs'
4
+
5
+
6
+ async function init() {
7
+
8
+ //check
9
+ let __dirname = path.dirname(fileURLToPath(import.meta.url))
10
+ if (!__dirname.includes('node_modules')) {
11
+ return //非位於node_modules, 代表套件本身
12
+ }
13
+
14
+ //autoDownloadFiles, postinstall時cwd=套件自身在node_modules內的目錄, 其依序查找<cwd>/src/與<cwd>/node_modules/w-html2docx/src/, 皆無則下載至<cwd>/src/,
15
+ //與執行期缺檔時之自動下載為同一套邏輯, 不另行組路徑; 已存在(重新安裝)則不重複下載
16
+ let { fpExe } = await autoDownloadFiles()
17
+ console.log(`htmlToDocx.exe is ready at [${fpExe}]`)
18
+
19
+ }
20
+ init()
21
+ .catch((err) => {
22
+ console.log(err)
23
+ })
24
+
25
+ //node scripts/install.mjs
@@ -1,169 +1,203 @@
1
- import path from 'path'
2
- import process from 'process'
3
- import get from 'lodash-es/get.js'
4
- import isestr from 'wsemi/src/isestr.mjs'
5
- import isearr from 'wsemi/src/isearr.mjs'
6
- import ispnum from 'wsemi/src/ispnum.mjs'
7
- import cdbl from 'wsemi/src/cdbl.mjs'
8
- import str2b64 from 'wsemi/src/str2b64.mjs'
9
- import execProcess from 'wsemi/src/execProcess.mjs'
10
- import fsIsFile from 'wsemi/src/fsIsFile.mjs'
11
- import autoDownloadFiles from './autoDownloadFiles.mjs'
12
-
13
-
14
- let fdSrv = path.resolve()
15
-
16
-
17
- function isWindows() {
18
- return process.platform === 'win32'
19
- }
20
-
21
-
22
- /**
23
- * Html檔轉Docx檔
24
- *
25
- * @param {String} fpInHtml 輸入來源Html檔位置字串
26
- * @param {String} fpOutDocx 輸入轉出Docx檔位置字串
27
- * @param {Object} [opt={}] 輸入設定物件,預設{}
28
- * @param {String} [opt.fpInTemp='./src/tmp.docx'] 輸入Docx模板檔案位置字串,預設'./src/tmp.docx'
29
- * @param {Array|String} [opt.fontFamilies=['標楷體','Times New Roman']] 輸入轉出Docx時強制更改字型陣列或字串,陣列順序為變更順序,若有要覆寫字型得放後面。預設['標楷體','Times New Roman']
30
- * @param {String} [opt.imgRatioWidthMax=1] 輸入圖片最大寬度比例,值介於0至1,預設1
31
- * @param {String} [opt.imgRatioHeightMax=1] 輸入圖片最大高度比例,值介於0至1,預設1
32
- * @returns {Promise} 回傳Promise,resolve回傳成功訊息,reject回傳錯誤訊息
33
- * @example
34
- *
35
- * import w from 'wsemi'
36
- * import WHtml2docx from './src/WHtml2docx.mjs'
37
- * //import WHtml2docx from 'w-html2docx/src/WHtml2docx.mjs'
38
- * //import WHtml2docx from 'w-html2docx'
39
- *
40
- * async function test() {
41
- *
42
- * let fpIn = `./test/ztmp.html`
43
- * let fpOut = `./test/ztmp.docx`
44
- * let opt = {
45
- * imgRatioWidthMax: 0.5,
46
- * }
47
- *
48
- * let r = await WHtml2docx(fpIn, fpOut, opt)
49
- * console.log(r)
50
- * // => ok
51
- *
52
- * w.fsDeleteFile(fpOut)
53
- *
54
- * }
55
- * test()
56
- * .catch((err) => {
57
- * console.log('catch', err)
58
- * })
59
- *
60
- */
61
- async function WHtml2docx(fpInHtml, fpOutDocx, opt = {}) {
62
- let errTemp = null
63
-
64
- //isWindows
65
- if (!isWindows()) {
66
- return Promise.reject('operating system is not windows')
67
- }
68
-
69
- //check
70
- if (!fsIsFile(fpInHtml)) {
71
- return Promise.reject(`fpInHtml[${fpInHtml}] does not exist`)
72
- }
73
-
74
- //fontFamilies
75
- let fontFamilies = get(opt, 'fontFamilies')
76
- if (isestr(fontFamilies)) {
77
- fontFamilies = [fontFamilies]
78
- }
79
- if (!isearr(fontFamilies)) {
80
- fontFamilies = ['標楷體', 'Times New Roman']
81
- }
82
-
83
- //imgRatioWidthMax
84
- let imgRatioWidthMax = get(opt, 'imgRatioWidthMax')
85
- if (!ispnum(imgRatioWidthMax)) {
86
- imgRatioWidthMax = 1
87
- }
88
- imgRatioWidthMax = cdbl(imgRatioWidthMax)
89
-
90
- //imgRatioHeightMax
91
- let imgRatioHeightMax = get(opt, 'imgRatioHeightMax')
92
- if (!ispnum(imgRatioHeightMax)) {
93
- imgRatioHeightMax = 1
94
- }
95
- imgRatioHeightMax = cdbl(imgRatioHeightMax)
96
-
97
- //fpInTemp
98
- let fpInTemp = get(opt, 'fpInTemp')
99
- if (!isestr(fpInTemp)) {
100
-
101
- //fnTmp
102
- let fnTmp = `tmp.docx`
103
-
104
- //fpInTemp
105
- if (true) {
106
- let fpTmpSrc = `${fdSrv}/src/${fnTmp}`
107
- let fpTmpNM = `${fdSrv}/node_modules/w-html2docx/src/${fnTmp}`
108
- if (fsIsFile(fpTmpSrc)) {
109
- fpInTemp = fpTmpSrc
110
- }
111
- else if (fsIsFile(fpTmpNM)) {
112
- fpInTemp = fpTmpNM
113
- }
114
- else {
115
- return Promise.reject(`can not find folder for ${fnTmp}`)
116
- }
117
-
118
- }
119
-
120
- }
121
-
122
- //轉絕對路徑
123
- fpInHtml = path.resolve(fpInHtml)
124
- fpInTemp = path.resolve(fpInTemp)
125
- fpOutDocx = path.resolve(fpOutDocx)
126
-
127
- //prog, 自動定位htmlToDocx.exe, 無檔案(安裝時npm封鎖scripts致postinstall未執行)則自動下載
128
- let { fpExe } = await autoDownloadFiles()
129
- let prog = fpExe
130
- // console.log('prog', prog)
131
-
132
- //inp
133
- let inp = {
134
- fpInSrc: fpInHtml,
135
- fpInTemp,
136
- fpOut: fpOutDocx,
137
- fontFamilies: ['標楷體', 'Times New Roman'],
138
- imgRatioWidthMax,
139
- imgRatioHeightMax,
140
- }
141
- // console.log('inp', inp)
142
-
143
- //input to b64
144
- let cInput = JSON.stringify(inp)
145
- let b64Input = str2b64(cInput)
146
- // console.log('b64Input', b64Input)
147
-
148
- //execProcess
149
- await execProcess(prog, b64Input)
150
- .catch((err) => {
151
- console.log('execProcess catch', err)
152
- errTemp = err.toString()
153
- })
154
-
155
- //check
156
- if (errTemp) {
157
- return Promise.reject(errTemp)
158
- }
159
-
160
- // //check
161
- // if (!isestr(output)) {
162
- // return Promise.reject(`output[${cstr(output)}] is not an effective string`)
163
- // }
164
-
165
- return 'ok'
166
- }
167
-
168
-
169
- export default WHtml2docx
1
+ import fs from 'fs'
2
+ import path from 'path'
3
+ import process from 'process'
4
+ import get from 'lodash-es/get.js'
5
+ import isestr from 'wsemi/src/isestr.mjs'
6
+ import isearr from 'wsemi/src/isearr.mjs'
7
+ import ispnum from 'wsemi/src/ispnum.mjs'
8
+ import cdbl from 'wsemi/src/cdbl.mjs'
9
+ import str2b64 from 'wsemi/src/str2b64.mjs'
10
+ import execProcess from 'wsemi/src/execProcess.mjs'
11
+ import fsIsFile from 'wsemi/src/fsIsFile.mjs'
12
+ import fsIsFolder from 'wsemi/src/fsIsFolder.mjs'
13
+ import fsCreateFolder from 'wsemi/src/fsCreateFolder.mjs'
14
+ import autoDownloadFiles from './autoDownloadFiles.mjs'
15
+
16
+
17
+ let fdSrv = path.resolve()
18
+
19
+
20
+ function isWindows() {
21
+ return process.platform === 'win32'
22
+ }
23
+
24
+
25
+ /**
26
+ * Html檔轉Docx
27
+ *
28
+ * 轉檔由套件內之htmlToDocx.exe調用本機Microsoft Word(win32com)完成,故須於Windows且已安裝Word之機器執行。htmlToDocx.exe若不存在(安裝時npm封鎖scripts致postinstall未執行)會於轉檔當下自動下載,下載失敗則reject並帶網址與落點。
29
+ * 轉檔器內部失敗(Word未安裝、模板無法開啟、另存失敗等)一律reject並帶轉檔器回報之原因;resolve前另確認輸出檔已產生且非空。輸出資料夾不存在時自動建立
30
+ *
31
+ * @param {String} fpInHtml 輸入來源Html檔位置字串
32
+ * @param {String} fpOutDocx 輸入轉出Docx檔位置字串
33
+ * @param {Object} [opt={}] 輸入設定物件,預設{}
34
+ * @param {String} [opt.fpInTemp=''] 輸入Docx模板檔案位置字串,有給時須存在否則reject;未給時依序取模組載入當下工作路徑之`./src/tmp.docx`或`./node_modules/w-html2docx/src/tmp.docx`(套件內建模板),預設''
35
+ * @param {Array|String} [opt.fontFamilies=['標楷體','Times New Roman']] 輸入轉出Docx時強制更改字型陣列或字串,陣列順序為變更順序,若有要覆寫字型得放後面。預設['標楷體','Times New Roman']
36
+ * @param {Number} [opt.imgRatioWidthMax=1] 輸入圖片最大寬度比例,值介於0至1,預設1
37
+ * @param {Number} [opt.imgRatioHeightMax=1] 輸入圖片最大高度比例,值介於0至1,預設1
38
+ * @param {Number} [opt.timeout=null] 輸入轉檔逾時毫秒數,預設null表示不限制;逾時時強制關閉轉檔器並reject回傳逾時訊息,惟由轉檔器經COM啟動之Word不在其程序樹內,不會一併被關閉
39
+ * @returns {Promise} 回傳Promise,resolve回傳'ok',reject回傳錯誤訊息字串
40
+ * @example
41
+ *
42
+ * import w from 'wsemi'
43
+ * import WHtml2docx from './src/WHtml2docx.mjs'
44
+ * //import WHtml2docx from 'w-html2docx/src/WHtml2docx.mjs'
45
+ * //import WHtml2docx from 'w-html2docx'
46
+ *
47
+ * async function test() {
48
+ *
49
+ * let fpIn = `./test/ztmp.html`
50
+ * let fpOut = `./test/ztmp.docx`
51
+ * let opt = {
52
+ * imgRatioWidthMax: 0.5,
53
+ * }
54
+ *
55
+ * let r = await WHtml2docx(fpIn, fpOut, opt)
56
+ * console.log(r)
57
+ * // => ok
58
+ *
59
+ * w.fsDeleteFile(fpOut)
60
+ *
61
+ * }
62
+ * test()
63
+ * .catch((err) => {
64
+ * console.log('catch', err)
65
+ * })
66
+ *
67
+ */
68
+ async function WHtml2docx(fpInHtml, fpOutDocx, opt = {}) {
69
+ let errTemp = null
70
+
71
+ //isWindows
72
+ if (!isWindows()) {
73
+ return Promise.reject('operating system is not windows')
74
+ }
75
+
76
+ //check
77
+ if (!fsIsFile(fpInHtml)) {
78
+ return Promise.reject(`fpInHtml[${fpInHtml}] does not exist`)
79
+ }
80
+
81
+ //fontFamilies
82
+ let fontFamilies = get(opt, 'fontFamilies')
83
+ if (isestr(fontFamilies)) {
84
+ fontFamilies = [fontFamilies]
85
+ }
86
+ if (!isearr(fontFamilies)) {
87
+ fontFamilies = ['標楷體', 'Times New Roman']
88
+ }
89
+
90
+ //imgRatioWidthMax
91
+ let imgRatioWidthMax = get(opt, 'imgRatioWidthMax')
92
+ if (!ispnum(imgRatioWidthMax)) {
93
+ imgRatioWidthMax = 1
94
+ }
95
+ imgRatioWidthMax = cdbl(imgRatioWidthMax)
96
+
97
+ //imgRatioHeightMax
98
+ let imgRatioHeightMax = get(opt, 'imgRatioHeightMax')
99
+ if (!ispnum(imgRatioHeightMax)) {
100
+ imgRatioHeightMax = 1
101
+ }
102
+ imgRatioHeightMax = cdbl(imgRatioHeightMax)
103
+
104
+ //timeout
105
+ let timeout = get(opt, 'timeout')
106
+ if (!ispnum(timeout)) {
107
+ timeout = null
108
+ }
109
+
110
+ //fpInTemp
111
+ let fpInTemp = get(opt, 'fpInTemp')
112
+ if (isestr(fpInTemp)) {
113
+
114
+ //check, 呼叫端有提供時須存在, 與fpInHtml之檢查對稱; 否則會由轉檔器於開啟模板時失敗, 訊息為Word之COM錯誤而看不出是路徑問題
115
+ if (!fsIsFile(fpInTemp)) {
116
+ return Promise.reject(`fpInTemp[${fpInTemp}] does not exist`)
117
+ }
118
+
119
+ }
120
+ else {
121
+
122
+ //fnTmp
123
+ let fnTmp = `tmp.docx`
124
+
125
+ //fpInTemp
126
+ if (true) {
127
+ let fpTmpSrc = `${fdSrv}/src/${fnTmp}`
128
+ let fpTmpNM = `${fdSrv}/node_modules/w-html2docx/src/${fnTmp}`
129
+ if (fsIsFile(fpTmpSrc)) {
130
+ fpInTemp = fpTmpSrc
131
+ }
132
+ else if (fsIsFile(fpTmpNM)) {
133
+ fpInTemp = fpTmpNM
134
+ }
135
+ else {
136
+ return Promise.reject(`can not find folder for ${fnTmp}`)
137
+ }
138
+
139
+ }
140
+
141
+ }
142
+
143
+ //轉絕對路徑
144
+ fpInHtml = path.resolve(fpInHtml)
145
+ fpInTemp = path.resolve(fpInTemp)
146
+ fpOutDocx = path.resolve(fpOutDocx)
147
+
148
+ //fdOutDocx, 輸出資料夾不存在時自動建立, 否則Word另存時失敗
149
+ let fdOutDocx = path.dirname(fpOutDocx)
150
+ if (!fsIsFolder(fdOutDocx)) {
151
+ fsCreateFolder(fdOutDocx)
152
+ if (!fsIsFolder(fdOutDocx)) {
153
+ return Promise.reject(`can not create folder[${fdOutDocx}] for fpOutDocx`)
154
+ }
155
+ }
156
+
157
+ //prog, 自動定位htmlToDocx.exe, 無檔案(安裝時npm封鎖scripts致postinstall未執行)則自動下載
158
+ let { fpExe } = await autoDownloadFiles()
159
+ let prog = fpExe
160
+ // console.log('prog', prog)
161
+
162
+ //inp
163
+ let inp = {
164
+ fpInSrc: fpInHtml,
165
+ fpInTemp,
166
+ fpOut: fpOutDocx,
167
+ fontFamilies,
168
+ imgRatioWidthMax,
169
+ imgRatioHeightMax,
170
+ }
171
+ // console.log('inp', inp)
172
+
173
+ //input to b64
174
+ let cInput = JSON.stringify(inp)
175
+ let b64Input = str2b64(cInput)
176
+ // console.log('b64Input', b64Input)
177
+
178
+ //execProcess, 轉檔器成功時印success且離開碼0, 失敗時印error: 原因且離開碼1, 故離開碼非0即reject並帶其輸出
179
+ // codeCmd給auto: 現行轉檔器以utf-8輸出, 尚未重新下載之舊版轉檔器以系統字碼頁(中文Windows為cp950)輸出, auto先以utf-8嚴格解碼, 遇非utf-8位元組改用系統字碼頁, 兩者皆可正確解碼; 轉檔器損毀時bootloader之ANSI訊息亦同
180
+ let output = ''
181
+ await execProcess(prog, b64Input, { codeCmd: 'auto', timeout })
182
+ .then((r) => {
183
+ output = r
184
+ })
185
+ .catch((err) => {
186
+ errTemp = String(err).trim()
187
+ })
188
+
189
+ //check
190
+ if (errTemp) {
191
+ return Promise.reject(errTemp)
192
+ }
193
+
194
+ //check, 轉檔器回報成功仍須確認輸出檔存在且非空: 舊版轉檔器於Word未安裝、模板無法開啟等失敗時仍以離開碼0結束, 只在輸出印出error
195
+ if (!fsIsFile(fpOutDocx) || fs.statSync(fpOutDocx).size === 0) {
196
+ return Promise.reject(`fpOutDocx[${fpOutDocx}] was not generated: ${output.trim()}`)
197
+ }
198
+
199
+ return 'ok'
200
+ }
201
+
202
+
203
+ export default WHtml2docx
@@ -18,6 +18,9 @@ let pmDownload = null
18
18
  * 依序偵測當前工作路徑的src/與node_modules/w-html2docx/src/,皆無htmlToDocx.exe時,
19
19
  * 代表安裝時npm封鎖scripts致postinstall未執行,故自動調用downloadFiles重新下載
20
20
  *
21
+ * 下載係先寫入暫存檔`htmlToDocx.exe.download`,完成後才改名為htmlToDocx.exe,故下載中途斷線或程序被終止時不會留下截斷之htmlToDocx.exe,下次呼叫會重新下載;
22
+ * 下載失敗(斷網、代理不可達、非2xx回應、中途斷線)時reject字串`failed to download url[網址] to file[落點]: 原因`
23
+ *
21
24
  * 供w-html2docx自身與其他依賴w-html2docx的套件調用,無須各自實作偵測與下載邏輯
22
25
  *
23
26
  * 因htmlToDocx.exe只能用於Windows作業系統,故調用前須自行檢核作業系統
@@ -66,12 +69,11 @@ async function autoDownloadFiles() {
66
69
  //併發呼叫共用同一個下載Promise, 避免重複下載
67
70
  if (pmDownload === null) {
68
71
  pmDownload = downloadFiles(fdBaseDL)
69
- .catch((err) => {
72
+ .finally(() => {
70
73
 
71
- //下載失敗歸零, 使下次呼叫可重試下載
74
+ //下載結束(成敗皆然)歸零, 使下次呼叫重新依檔案是否存在判定; 若只於失敗時歸零, 下載成功後檔案被移除(重裝套件、防毒隔離)或程序切換工作路徑時, 會沿用已resolve之Promise而不再下載
72
75
  pmDownload = null
73
76
 
74
- return Promise.reject(err)
75
77
  })
76
78
  }
77
79
  await pmDownload
@@ -85,7 +87,7 @@ async function autoDownloadFiles() {
85
87
 
86
88
  //check
87
89
  if (fdBase === '') {
88
- return Promise.reject('can not find htmlToDocx.exe')
90
+ return Promise.reject(`can not find ${fnExe} in [${fdBaseSelf}] or [${fdBaseNM}]`)
89
91
  }
90
92
 
91
93
  //fpExe