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.
- package/README.md +1 -1
- package/dist/w-html2docx.umd.js +2 -2
- package/dist/w-html2docx.umd.js.map +1 -1
- package/docs/WHtml2docx.mjs.html +204 -170
- package/docs/autoDownloadFiles.mjs.html +7 -5
- package/docs/global.html +77 -10
- package/docs/index.html +1 -1
- package/package.json +2 -2
- package/scripts/install.mjs +25 -29
- package/src/WHtml2docx.mjs +203 -169
- package/src/autoDownloadFiles.mjs +6 -4
- package/srcPython/htmlToDocx.py +416 -385
- package/srcPython//350/252/252/346/230/216.txt +32 -24
- package/test/WHtml2docx.test.mjs +51 -0
package/srcPython/htmlToDocx.py
CHANGED
|
@@ -1,385 +1,416 @@
|
|
|
1
|
-
from win32com import client as wc
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
#
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
#
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
es
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
'
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
err
|
|
36
|
-
|
|
37
|
-
'
|
|
38
|
-
'
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
#
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
#
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
#
|
|
112
|
-
#
|
|
113
|
-
#
|
|
114
|
-
# app.Selection.
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
#
|
|
198
|
-
#
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
#
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
#
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
1
|
+
from win32com import client as wc
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
#使用win32com
|
|
5
|
+
#win32com因更新問題會出現 ImportError: DLL load failed while importing win32api: 找不到指定的模組。
|
|
6
|
+
#1. 安裝 pip install pywin32
|
|
7
|
+
#2. 使用系統管理員權限開啟cmd
|
|
8
|
+
#3. cd至安裝目錄 C:\ProgramData\Anaconda3\Scripts
|
|
9
|
+
#4. 用python安裝腳本 python pywin32_postinstall.py -install
|
|
10
|
+
|
|
11
|
+
#編譯
|
|
12
|
+
#1. 安裝編譯套件 pip install pyinstaller
|
|
13
|
+
#2. 編譯 pyinstaller -F htmlToDocx.py
|
|
14
|
+
|
|
15
|
+
#win32com教學
|
|
16
|
+
#https://zhuanlan.zhihu.com/p/67543981
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def getError():
|
|
20
|
+
import sys
|
|
21
|
+
|
|
22
|
+
#exc_info
|
|
23
|
+
type, message, traceback = sys.exc_info()
|
|
24
|
+
|
|
25
|
+
#es
|
|
26
|
+
es=[]
|
|
27
|
+
while traceback:
|
|
28
|
+
e={
|
|
29
|
+
'name':traceback.tb_frame.f_code.co_name,
|
|
30
|
+
'filename':traceback.tb_frame.f_code.co_filename,
|
|
31
|
+
}
|
|
32
|
+
es.append(e)
|
|
33
|
+
traceback = traceback.tb_next
|
|
34
|
+
|
|
35
|
+
#err
|
|
36
|
+
err={
|
|
37
|
+
'type':type,
|
|
38
|
+
'message':message,
|
|
39
|
+
'traceback':es,
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return err
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def j2o(v):
|
|
46
|
+
#json轉物件
|
|
47
|
+
import json
|
|
48
|
+
return json.loads(v)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def o2j(v):
|
|
52
|
+
#物件轉json
|
|
53
|
+
import json
|
|
54
|
+
return json.dumps(v, ensure_ascii=False)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def str2b64(v):
|
|
58
|
+
#字串轉base64字串
|
|
59
|
+
import base64
|
|
60
|
+
v=base64.b64encode(v.encode('utf-8'))
|
|
61
|
+
return str(v,'utf-8')
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def b642str(v):
|
|
65
|
+
#base64字串轉字串
|
|
66
|
+
import base64
|
|
67
|
+
return base64.b64decode(v).decode('utf-8')
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def readText(fn):
|
|
71
|
+
#讀取檔案fn內文字
|
|
72
|
+
import codecs
|
|
73
|
+
with codecs.open(fn,'r',encoding='utf8') as f:
|
|
74
|
+
return f.read()
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def writeText(fn,str):
|
|
78
|
+
#寫出文字str至檔案fn
|
|
79
|
+
import codecs
|
|
80
|
+
with codecs.open(fn,'w',encoding='utf8') as f:
|
|
81
|
+
f.write(str)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def htmlToDocxCore(app, docs, fpInSrc, fpInTemp, fpOut, opt):
|
|
85
|
+
|
|
86
|
+
#Open, 開啟之文件推入docs, 供htmlToDocx於finally關閉
|
|
87
|
+
docInTemp = app.Documents.Open(fpInTemp)
|
|
88
|
+
docs.append(docInTemp)
|
|
89
|
+
docInSrc = app.Documents.Open(fpInSrc)
|
|
90
|
+
docs.append(docInSrc)
|
|
91
|
+
|
|
92
|
+
# 取得來源文件全文 Range(含格式)
|
|
93
|
+
rngSrc = docInSrc.Range()
|
|
94
|
+
|
|
95
|
+
# 取得模板文件的主內容 Range
|
|
96
|
+
rngDst = docInTemp.Content # 或 docInTemp.Range()
|
|
97
|
+
|
|
98
|
+
# 如果你想插在文件最後,可先收合到結尾
|
|
99
|
+
WdCollapseEnd = 0 #wdCollapseEnd, 收合到Range結尾
|
|
100
|
+
rngDst.Collapse(WdCollapseEnd)
|
|
101
|
+
|
|
102
|
+
# 直接複製格式化文字(等同貼上,但不經過剪貼簿也不靠 Selection)
|
|
103
|
+
rngDst.FormattedText = rngSrc.FormattedText
|
|
104
|
+
|
|
105
|
+
# #選擇src並複製全文
|
|
106
|
+
# docInSrc.Activate()
|
|
107
|
+
# app.Selection.WholeStory()
|
|
108
|
+
# app.Selection.Copy()
|
|
109
|
+
|
|
110
|
+
# #選擇模板並貼上全文
|
|
111
|
+
# docInTemp.Activate()
|
|
112
|
+
# app.Selection.Paste()
|
|
113
|
+
# # WdFormatOriginalFormatting = 16
|
|
114
|
+
# # app.Selection.PasteAndFormat(WdFormatOriginalFormatting)
|
|
115
|
+
|
|
116
|
+
#選擇模板調整字型
|
|
117
|
+
try:
|
|
118
|
+
|
|
119
|
+
fontFamilies=opt['fontFamilies']
|
|
120
|
+
|
|
121
|
+
if isinstance(fontFamilies, (list, tuple)) and len(fontFamilies) > 0:
|
|
122
|
+
#print(fontFamilies)
|
|
123
|
+
|
|
124
|
+
rng = docInTemp.Range()
|
|
125
|
+
for fn in fontFamilies:
|
|
126
|
+
# print(fn)
|
|
127
|
+
rng.Font.Name = fn
|
|
128
|
+
|
|
129
|
+
except:
|
|
130
|
+
err=getError()
|
|
131
|
+
print(err, file=sys.stderr)
|
|
132
|
+
|
|
133
|
+
#選擇模板偵測各圖片寬度是否大於滿版(412), 並限制於最大值
|
|
134
|
+
try:
|
|
135
|
+
|
|
136
|
+
imgRatioWidthMax=opt['imgRatioWidthMax']
|
|
137
|
+
imgRatioHeightMax=opt['imgRatioHeightMax']
|
|
138
|
+
|
|
139
|
+
page_setup = docInTemp.PageSetup
|
|
140
|
+
pageW = page_setup.PageWidth #A4≈595 pt
|
|
141
|
+
pageH = page_setup.PageHeight #A4≈842 pt
|
|
142
|
+
leftM = page_setup.LeftMargin
|
|
143
|
+
rightM = page_setup.RightMargin
|
|
144
|
+
topM = page_setup.TopMargin
|
|
145
|
+
bottomM = page_setup.BottomMargin
|
|
146
|
+
|
|
147
|
+
#版心最大maxW, maxH
|
|
148
|
+
maxW = pageW - leftM - rightM
|
|
149
|
+
maxW = maxW * imgRatioWidthMax
|
|
150
|
+
maxH = (pageH - topM - bottomM) * 0.937043054427296 #高度須預留圖名空間
|
|
151
|
+
maxH = maxH * imgRatioHeightMax
|
|
152
|
+
|
|
153
|
+
# 使用 docInTemp.InlineShapes 直接取得整份文件所有圖片
|
|
154
|
+
for s in docInTemp.InlineShapes:
|
|
155
|
+
try:
|
|
156
|
+
|
|
157
|
+
#w, h
|
|
158
|
+
w = s.Width
|
|
159
|
+
h = s.Height
|
|
160
|
+
|
|
161
|
+
#scale, 計算縮放比例
|
|
162
|
+
scaleW = maxW / w if w > maxW else 1
|
|
163
|
+
scaleH = maxH / h if h > maxH else 1
|
|
164
|
+
scale = min(scaleW, scaleH)
|
|
165
|
+
|
|
166
|
+
if scale < 1:
|
|
167
|
+
s.LockAspectRatio = True
|
|
168
|
+
s.Width = w * scale #只設width, LockAspectRatio=true會讓height依比例調整
|
|
169
|
+
|
|
170
|
+
except:
|
|
171
|
+
pass
|
|
172
|
+
|
|
173
|
+
except:
|
|
174
|
+
err=getError()
|
|
175
|
+
print(err, file=sys.stderr)
|
|
176
|
+
|
|
177
|
+
#設定前後行距
|
|
178
|
+
try:
|
|
179
|
+
|
|
180
|
+
for p in docInTemp.Paragraphs:
|
|
181
|
+
|
|
182
|
+
try:
|
|
183
|
+
lf = p.Range.ListFormat
|
|
184
|
+
except:
|
|
185
|
+
#若可能沒有ListFormat須跳過
|
|
186
|
+
continue
|
|
187
|
+
|
|
188
|
+
listType = lf.ListType # 0,1,2,3,4
|
|
189
|
+
listStr = lf.ListString # '•', '1.', ''...
|
|
190
|
+
|
|
191
|
+
# #測試顯示ListType
|
|
192
|
+
# try:
|
|
193
|
+
# rng = p.Range.Duplicate
|
|
194
|
+
# rng.End = rng.End - 1 #最末是換行符號, 不能插在換行符號後面
|
|
195
|
+
# rng.InsertAfter(f"[{listType},{listStr}]")
|
|
196
|
+
# except:
|
|
197
|
+
# err = getError()
|
|
198
|
+
# print(err)
|
|
199
|
+
|
|
200
|
+
WdListNoNumbering = 0 #普通段落
|
|
201
|
+
WdListSingleLevelNumber = 1 #單層編號清單
|
|
202
|
+
WdListMultiLevel = 2 #多層編號清單
|
|
203
|
+
WdListBullet = 3 #項目符號清單
|
|
204
|
+
WdListOutlineNumbering = 4 #大綱編號
|
|
205
|
+
is_list = (listType != WdListNoNumbering) or (listStr != "")
|
|
206
|
+
|
|
207
|
+
if is_list:
|
|
208
|
+
|
|
209
|
+
fmt = p.Format
|
|
210
|
+
|
|
211
|
+
#關閉自動段前段後距離
|
|
212
|
+
try:
|
|
213
|
+
fmt.SpaceBeforeAuto = False
|
|
214
|
+
fmt.SpaceAfterAuto = False
|
|
215
|
+
except:
|
|
216
|
+
pass
|
|
217
|
+
|
|
218
|
+
# 設定為 0.5 行
|
|
219
|
+
fmt.LineUnitBefore = 0.5
|
|
220
|
+
fmt.LineUnitAfter = 0.5
|
|
221
|
+
|
|
222
|
+
except:
|
|
223
|
+
err=getError()
|
|
224
|
+
print(err, file=sys.stderr)
|
|
225
|
+
|
|
226
|
+
#設定本文段落(p)與清單項目(li)之水平對齊, 已為置中者(如表格標題列, 圖名)不更動, 其餘改為左右對齊(兩端對齊), 使右側邊界齊平較為美觀
|
|
227
|
+
try:
|
|
228
|
+
|
|
229
|
+
WdAlignParagraphCenter = 1 #置中對齊
|
|
230
|
+
WdAlignParagraphJustify = 3 #左右對齊
|
|
231
|
+
WdOutlineLevelBodyText = 10 #本文層級, 標題(h1-h6)匯入後為大綱層級1-9
|
|
232
|
+
WdWithInTable = 12 #Information之參數, 判斷是否位於表格內
|
|
233
|
+
|
|
234
|
+
for p in docInTemp.Paragraphs:
|
|
235
|
+
try:
|
|
236
|
+
|
|
237
|
+
rng = p.Range
|
|
238
|
+
|
|
239
|
+
#跳過標題段落
|
|
240
|
+
if p.OutlineLevel != WdOutlineLevelBodyText:
|
|
241
|
+
continue
|
|
242
|
+
|
|
243
|
+
#跳過表格內段落
|
|
244
|
+
if rng.Information(WdWithInTable):
|
|
245
|
+
continue
|
|
246
|
+
|
|
247
|
+
#跳過圖片段落
|
|
248
|
+
if rng.InlineShapes.Count > 0:
|
|
249
|
+
continue
|
|
250
|
+
|
|
251
|
+
#跳過置中段落
|
|
252
|
+
if p.Alignment == WdAlignParagraphCenter:
|
|
253
|
+
continue
|
|
254
|
+
|
|
255
|
+
p.Alignment = WdAlignParagraphJustify
|
|
256
|
+
|
|
257
|
+
except:
|
|
258
|
+
pass
|
|
259
|
+
|
|
260
|
+
except:
|
|
261
|
+
err=getError()
|
|
262
|
+
print(err, file=sys.stderr)
|
|
263
|
+
|
|
264
|
+
#移除零寬空格佔位字元(U+200B): 來源為w-md2html於換行標記空div內插入之佔位字元, 用以撐過Word匯入不被當成空段落丟棄
|
|
265
|
+
#於此移除後該段落成為真正的空段落(乾淨段落標記, 顯示編輯標記時亦無可見字元)
|
|
266
|
+
try:
|
|
267
|
+
|
|
268
|
+
z = chr(0x200B) #U+200B 零寬空格
|
|
269
|
+
|
|
270
|
+
for p in docInTemp.Paragraphs:
|
|
271
|
+
rng = p.Range.Duplicate
|
|
272
|
+
rng.End = rng.End - 1 #排除段落結尾之段落標記, 避免破壞段落結構
|
|
273
|
+
t = rng.Text
|
|
274
|
+
if z in t:
|
|
275
|
+
rng.Text = t.replace(z, '') #移除佔位字元, 段落標記保留 => 乾淨空段落
|
|
276
|
+
|
|
277
|
+
except:
|
|
278
|
+
err=getError()
|
|
279
|
+
print(err, file=sys.stderr)
|
|
280
|
+
|
|
281
|
+
#SaveAs
|
|
282
|
+
WdSaveFormat = 16 #wdFormatDocumentDefault=16(docx) #https://learn.microsoft.com/zh-tw/office/vba/api/word.wdsaveformat
|
|
283
|
+
docInTemp.SaveAs2(fpOut, WdSaveFormat)
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def htmlToDocx(fpInSrc, fpInTemp, fpOut, opt):
|
|
287
|
+
|
|
288
|
+
#Dispatch
|
|
289
|
+
app = wc.Dispatch('Word.Application')
|
|
290
|
+
|
|
291
|
+
#正式版須隱藏
|
|
292
|
+
app.Visible = False
|
|
293
|
+
|
|
294
|
+
#不詢問使用者
|
|
295
|
+
app.DisplayAlerts = False
|
|
296
|
+
|
|
297
|
+
#docs, 已開啟之文件, 由htmlToDocxCore推入
|
|
298
|
+
docs = []
|
|
299
|
+
|
|
300
|
+
#任一步驟例外時亦須關閉已開啟之文件並結束Word, 否則殘留之WINWORD會鎖住模板與輸出檔, 使後續轉檔失敗
|
|
301
|
+
try:
|
|
302
|
+
htmlToDocxCore(app, docs, fpInSrc, fpInTemp, fpOut, opt)
|
|
303
|
+
finally:
|
|
304
|
+
|
|
305
|
+
# Close
|
|
306
|
+
WdDoNotSaveChanges = 0 #wdDoNotSaveChanges
|
|
307
|
+
for doc in reversed(docs):
|
|
308
|
+
try:
|
|
309
|
+
doc.Close(WdDoNotSaveChanges)
|
|
310
|
+
except:
|
|
311
|
+
pass
|
|
312
|
+
|
|
313
|
+
#Quit
|
|
314
|
+
WdSaveOptions = 0 #wdDoNotSaveChanges=0(不儲存擱置中變更) #https://learn.microsoft.com/zh-tw/office/vba/api/word.wdsaveoptions
|
|
315
|
+
try:
|
|
316
|
+
app.Quit(WdSaveOptions)
|
|
317
|
+
except:
|
|
318
|
+
pass
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def core(b64):
|
|
322
|
+
state=''
|
|
323
|
+
|
|
324
|
+
try:
|
|
325
|
+
|
|
326
|
+
#b642str
|
|
327
|
+
s=b642str(b64)
|
|
328
|
+
|
|
329
|
+
#j2o
|
|
330
|
+
o=j2o(s)
|
|
331
|
+
|
|
332
|
+
#params
|
|
333
|
+
fpInSrc=o['fpInSrc']
|
|
334
|
+
fpInTemp=o['fpInTemp']
|
|
335
|
+
fpOut=o['fpOut']
|
|
336
|
+
|
|
337
|
+
#opt
|
|
338
|
+
opt={}
|
|
339
|
+
opt['fontFamilies']=o['fontFamilies']
|
|
340
|
+
opt['imgRatioWidthMax']=o['imgRatioWidthMax']
|
|
341
|
+
opt['imgRatioHeightMax']=o['imgRatioHeightMax']
|
|
342
|
+
|
|
343
|
+
#htmlToDocx
|
|
344
|
+
htmlToDocx(fpInSrc, fpInTemp, fpOut, opt)
|
|
345
|
+
|
|
346
|
+
state='success'
|
|
347
|
+
except:
|
|
348
|
+
err=getError()
|
|
349
|
+
state='error: '+err['type'].__name__+': '+str(err['message'])
|
|
350
|
+
|
|
351
|
+
return state
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
def run():
|
|
355
|
+
|
|
356
|
+
#stdout與stderr改為utf-8: Python寫pipe時預設用系統ANSI字碼頁(中文Windows為cp950), 呼叫端須猜編碼才能讀, 且訊息含cp950無法編碼之字元時print會拋UnicodeEncodeError;
|
|
357
|
+
#PyInstaller打包後不吃PYTHONIOENCODING與PYTHONUTF8, 只能於此設定
|
|
358
|
+
for f in (sys.stdout, sys.stderr):
|
|
359
|
+
try:
|
|
360
|
+
f.reconfigure(encoding='utf-8', errors='backslashreplace')
|
|
361
|
+
except:
|
|
362
|
+
pass
|
|
363
|
+
|
|
364
|
+
#由外部程序呼叫或直接給予檔案路徑
|
|
365
|
+
state=''
|
|
366
|
+
argv=sys.argv
|
|
367
|
+
#argv=['','']
|
|
368
|
+
if len(argv)==2:
|
|
369
|
+
|
|
370
|
+
#b64
|
|
371
|
+
b64=sys.argv[1]
|
|
372
|
+
|
|
373
|
+
#core
|
|
374
|
+
state=core(b64)
|
|
375
|
+
|
|
376
|
+
else:
|
|
377
|
+
#print(sys.argv)
|
|
378
|
+
state='error: invalid length of argv'
|
|
379
|
+
|
|
380
|
+
#print & flush
|
|
381
|
+
print(state)
|
|
382
|
+
sys.stdout.flush()
|
|
383
|
+
|
|
384
|
+
#失敗時以非0離開碼結束, 使呼叫端(如wsemi之execProcess)能直接以離開碼判定成敗, 不必解析輸出
|
|
385
|
+
if state != 'success':
|
|
386
|
+
sys.exit(1)
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
if True:
|
|
390
|
+
#正式版
|
|
391
|
+
|
|
392
|
+
#run
|
|
393
|
+
run()
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
if False:
|
|
397
|
+
#產生測試輸入b64
|
|
398
|
+
|
|
399
|
+
#inp
|
|
400
|
+
inp={
|
|
401
|
+
'fpInSrc':'./ztmp.html',
|
|
402
|
+
'fpInTemp':'./tmp.docx',
|
|
403
|
+
'fpOut':'./ztmp.docx',
|
|
404
|
+
'fontFamilies': ['標楷體','Times New Roman'], #word使用, 因由左往右設定, 故得先設定標楷體再設定Times New Roman
|
|
405
|
+
}
|
|
406
|
+
# print(o2j(inp))
|
|
407
|
+
|
|
408
|
+
#str2b64
|
|
409
|
+
b64=str2b64(o2j(inp))
|
|
410
|
+
print(b64)
|
|
411
|
+
|
|
412
|
+
#core
|
|
413
|
+
state=core(b64)
|
|
414
|
+
|
|
415
|
+
print(state)
|
|
416
|
+
|