pyxllib 0.0.43__py3-none-any.whl → 0.3.197__py3-none-any.whl

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 (186) hide show
  1. pyxllib/__init__.py +9 -2
  2. pyxllib/algo/__init__.py +8 -0
  3. pyxllib/algo/disjoint.py +54 -0
  4. pyxllib/algo/geo.py +541 -0
  5. pyxllib/{util/mathlib.py → algo/intervals.py} +172 -36
  6. pyxllib/algo/matcher.py +389 -0
  7. pyxllib/algo/newbie.py +166 -0
  8. pyxllib/algo/pupil.py +629 -0
  9. pyxllib/algo/shapelylib.py +67 -0
  10. pyxllib/algo/specialist.py +241 -0
  11. pyxllib/algo/stat.py +494 -0
  12. pyxllib/algo/treelib.py +149 -0
  13. pyxllib/algo/unitlib.py +66 -0
  14. pyxllib/autogui/__init__.py +5 -0
  15. pyxllib/autogui/activewin.py +246 -0
  16. pyxllib/autogui/all.py +9 -0
  17. pyxllib/autogui/autogui.py +852 -0
  18. pyxllib/autogui/uiautolib.py +362 -0
  19. pyxllib/autogui/virtualkey.py +102 -0
  20. pyxllib/autogui/wechat.py +827 -0
  21. pyxllib/autogui/wechat_msg.py +421 -0
  22. pyxllib/autogui/wxautolib.py +84 -0
  23. pyxllib/cv/__init__.py +1 -11
  24. pyxllib/cv/expert.py +267 -0
  25. pyxllib/cv/{imlib.py → imfile.py} +18 -83
  26. pyxllib/cv/imhash.py +39 -0
  27. pyxllib/cv/pupil.py +9 -0
  28. pyxllib/cv/rgbfmt.py +1525 -0
  29. pyxllib/cv/slidercaptcha.py +137 -0
  30. pyxllib/cv/trackbartools.py +163 -49
  31. pyxllib/cv/xlcvlib.py +1040 -0
  32. pyxllib/cv/xlpillib.py +423 -0
  33. pyxllib/data/__init__.py +0 -0
  34. pyxllib/data/echarts.py +240 -0
  35. pyxllib/data/jsonlib.py +89 -0
  36. pyxllib/{util/oss2_.py → data/oss.py} +11 -9
  37. pyxllib/data/pglib.py +1127 -0
  38. pyxllib/data/sqlite.py +568 -0
  39. pyxllib/{util → data}/sqllib.py +13 -31
  40. pyxllib/ext/JLineViewer.py +505 -0
  41. pyxllib/ext/__init__.py +6 -0
  42. pyxllib/{util → ext}/demolib.py +119 -35
  43. pyxllib/ext/drissionlib.py +277 -0
  44. pyxllib/ext/kq5034lib.py +12 -0
  45. pyxllib/{util/main.py → ext/old.py} +122 -284
  46. pyxllib/ext/qt.py +449 -0
  47. pyxllib/ext/robustprocfile.py +497 -0
  48. pyxllib/ext/seleniumlib.py +76 -0
  49. pyxllib/{util/tklib.py → ext/tk.py} +10 -11
  50. pyxllib/ext/unixlib.py +827 -0
  51. pyxllib/ext/utools.py +351 -0
  52. pyxllib/{util/webhooklib.py → ext/webhook.py} +45 -17
  53. pyxllib/ext/win32lib.py +40 -0
  54. pyxllib/ext/wjxlib.py +88 -0
  55. pyxllib/ext/wpsapi.py +124 -0
  56. pyxllib/ext/xlwork.py +9 -0
  57. pyxllib/ext/yuquelib.py +1105 -0
  58. pyxllib/file/__init__.py +17 -0
  59. pyxllib/file/docxlib.py +761 -0
  60. pyxllib/{util → file}/gitlib.py +40 -27
  61. pyxllib/file/libreoffice.py +165 -0
  62. pyxllib/file/movielib.py +148 -0
  63. pyxllib/file/newbie.py +10 -0
  64. pyxllib/file/onenotelib.py +1469 -0
  65. pyxllib/file/packlib/__init__.py +330 -0
  66. pyxllib/{util → file/packlib}/zipfile.py +598 -195
  67. pyxllib/file/pdflib.py +426 -0
  68. pyxllib/file/pupil.py +185 -0
  69. pyxllib/file/specialist/__init__.py +685 -0
  70. pyxllib/{basic/_5_dirlib.py → file/specialist/dirlib.py} +364 -93
  71. pyxllib/file/specialist/download.py +193 -0
  72. pyxllib/file/specialist/filelib.py +2829 -0
  73. pyxllib/file/xlsxlib.py +3131 -0
  74. pyxllib/file/xlsyncfile.py +341 -0
  75. pyxllib/prog/__init__.py +5 -0
  76. pyxllib/prog/cachetools.py +64 -0
  77. pyxllib/prog/deprecatedlib.py +233 -0
  78. pyxllib/prog/filelock.py +42 -0
  79. pyxllib/prog/ipyexec.py +253 -0
  80. pyxllib/prog/multiprogs.py +940 -0
  81. pyxllib/prog/newbie.py +451 -0
  82. pyxllib/prog/pupil.py +1197 -0
  83. pyxllib/{sitepackages.py → prog/sitepackages.py} +5 -3
  84. pyxllib/prog/specialist/__init__.py +391 -0
  85. pyxllib/prog/specialist/bc.py +203 -0
  86. pyxllib/prog/specialist/browser.py +497 -0
  87. pyxllib/prog/specialist/common.py +347 -0
  88. pyxllib/prog/specialist/datetime.py +199 -0
  89. pyxllib/prog/specialist/tictoc.py +240 -0
  90. pyxllib/prog/specialist/xllog.py +180 -0
  91. pyxllib/prog/xlosenv.py +108 -0
  92. pyxllib/stdlib/__init__.py +17 -0
  93. pyxllib/{util → stdlib}/tablepyxl/__init__.py +1 -3
  94. pyxllib/{util → stdlib}/tablepyxl/style.py +1 -1
  95. pyxllib/{util → stdlib}/tablepyxl/tablepyxl.py +2 -4
  96. pyxllib/text/__init__.py +8 -0
  97. pyxllib/text/ahocorasick.py +39 -0
  98. pyxllib/text/airscript.js +744 -0
  99. pyxllib/text/charclasslib.py +121 -0
  100. pyxllib/text/jiebalib.py +267 -0
  101. pyxllib/text/jinjalib.py +32 -0
  102. pyxllib/text/jsa_ai_prompt.md +271 -0
  103. pyxllib/text/jscode.py +922 -0
  104. pyxllib/text/latex/__init__.py +158 -0
  105. pyxllib/text/levenshtein.py +303 -0
  106. pyxllib/text/nestenv.py +1215 -0
  107. pyxllib/text/newbie.py +300 -0
  108. pyxllib/text/pupil/__init__.py +8 -0
  109. pyxllib/text/pupil/common.py +1121 -0
  110. pyxllib/text/pupil/xlalign.py +326 -0
  111. pyxllib/text/pycode.py +47 -0
  112. pyxllib/text/specialist/__init__.py +8 -0
  113. pyxllib/text/specialist/common.py +112 -0
  114. pyxllib/text/specialist/ptag.py +186 -0
  115. pyxllib/text/spellchecker.py +172 -0
  116. pyxllib/text/templates/echart_base.html +11 -0
  117. pyxllib/text/templates/highlight_code.html +17 -0
  118. pyxllib/text/templates/latex_editor.html +103 -0
  119. pyxllib/text/vbacode.py +17 -0
  120. pyxllib/text/xmllib.py +747 -0
  121. pyxllib/xl.py +39 -0
  122. pyxllib/xlcv.py +17 -0
  123. pyxllib-0.3.197.dist-info/METADATA +48 -0
  124. pyxllib-0.3.197.dist-info/RECORD +126 -0
  125. {pyxllib-0.0.43.dist-info → pyxllib-0.3.197.dist-info}/WHEEL +4 -5
  126. pyxllib/basic/_1_strlib.py +0 -945
  127. pyxllib/basic/_2_timelib.py +0 -488
  128. pyxllib/basic/_3_pathlib.py +0 -916
  129. pyxllib/basic/_4_loglib.py +0 -419
  130. pyxllib/basic/__init__.py +0 -54
  131. pyxllib/basic/arrow_.py +0 -250
  132. pyxllib/basic/chardet_.py +0 -66
  133. pyxllib/basic/dirlib.py +0 -529
  134. pyxllib/basic/dprint.py +0 -202
  135. pyxllib/basic/extension.py +0 -12
  136. pyxllib/basic/judge.py +0 -31
  137. pyxllib/basic/log.py +0 -204
  138. pyxllib/basic/pathlib_.py +0 -705
  139. pyxllib/basic/pytictoc.py +0 -102
  140. pyxllib/basic/qiniu_.py +0 -61
  141. pyxllib/basic/strlib.py +0 -761
  142. pyxllib/basic/timer.py +0 -132
  143. pyxllib/cv/cv.py +0 -834
  144. pyxllib/cv/cvlib/_1_geo.py +0 -543
  145. pyxllib/cv/cvlib/_2_cvprcs.py +0 -309
  146. pyxllib/cv/cvlib/_2_imgproc.py +0 -594
  147. pyxllib/cv/cvlib/_3_pilprcs.py +0 -80
  148. pyxllib/cv/cvlib/_4_cvimg.py +0 -211
  149. pyxllib/cv/cvlib/__init__.py +0 -10
  150. pyxllib/cv/debugtools.py +0 -82
  151. pyxllib/cv/fitz_.py +0 -300
  152. pyxllib/cv/installer.py +0 -42
  153. pyxllib/debug/_0_installer.py +0 -38
  154. pyxllib/debug/_1_typelib.py +0 -277
  155. pyxllib/debug/_2_chrome.py +0 -198
  156. pyxllib/debug/_3_showdir.py +0 -161
  157. pyxllib/debug/_4_bcompare.py +0 -140
  158. pyxllib/debug/__init__.py +0 -49
  159. pyxllib/debug/bcompare.py +0 -132
  160. pyxllib/debug/chrome.py +0 -198
  161. pyxllib/debug/installer.py +0 -38
  162. pyxllib/debug/showdir.py +0 -158
  163. pyxllib/debug/typelib.py +0 -278
  164. pyxllib/image/__init__.py +0 -12
  165. pyxllib/torch/__init__.py +0 -20
  166. pyxllib/torch/modellib.py +0 -37
  167. pyxllib/torch/trainlib.py +0 -344
  168. pyxllib/util/__init__.py +0 -20
  169. pyxllib/util/aip_.py +0 -141
  170. pyxllib/util/casiadb.py +0 -59
  171. pyxllib/util/excellib.py +0 -495
  172. pyxllib/util/filelib.py +0 -612
  173. pyxllib/util/jsondata.py +0 -27
  174. pyxllib/util/jsondata2.py +0 -92
  175. pyxllib/util/labelmelib.py +0 -139
  176. pyxllib/util/onepy/__init__.py +0 -29
  177. pyxllib/util/onepy/onepy.py +0 -574
  178. pyxllib/util/onepy/onmanager.py +0 -170
  179. pyxllib/util/pyautogui_.py +0 -219
  180. pyxllib/util/textlib.py +0 -1305
  181. pyxllib/util/unorder.py +0 -22
  182. pyxllib/util/xmllib.py +0 -639
  183. pyxllib-0.0.43.dist-info/METADATA +0 -39
  184. pyxllib-0.0.43.dist-info/RECORD +0 -80
  185. pyxllib-0.0.43.dist-info/top_level.txt +0 -1
  186. {pyxllib-0.0.43.dist-info → pyxllib-0.3.197.dist-info/licenses}/LICENSE +0 -0
@@ -0,0 +1,761 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ # @Author : 陈坤泽
4
+ # @Email : 877362867@qq.com
5
+ # @Date : 2021/08/31 09:56
6
+
7
+ from pyxllib.prog.pupil import check_install_package
8
+
9
+ check_install_package('win32com', 'pywin32')
10
+ check_install_package('docx', 'python-docx')
11
+
12
+ import json
13
+ import os
14
+ import re
15
+
16
+ import pythoncom
17
+ from win32com.client import constants
18
+ import win32com.client as win32
19
+ import docx
20
+ import docx.table
21
+ import docx.enum
22
+
23
+ from pyxllib.prog.pupil import DictTool, inject_members, run_once
24
+ from pyxllib.prog.specialist import get_etag, browser, XlPath
25
+ from pyxllib.text.pupil import strwidth
26
+
27
+
28
+ def __docx():
29
+ """ python-docx 相关封装
30
+ """
31
+ pass
32
+
33
+
34
+ class DocxTools:
35
+ @classmethod
36
+ def to_pdf(cls, docx_file, pdf_file=None):
37
+ check_install_package('docx2pdf') # 安装不成功的时候可以考虑加参数:--user
38
+ import docx2pdf
39
+
40
+ if pdf_file is None:
41
+ pdf_file = docx_file.with_suffix('.pdf')
42
+
43
+ docx2pdf.convert(str(docx_file), str(pdf_file))
44
+ return pdf_file
45
+
46
+ @classmethod
47
+ def merge(cls, master_file, toc, *, outline='demote'):
48
+ """ 合并多份docx文件
49
+
50
+ :param master_file: 要合并到哪个主文件
51
+ 注意如果这个文件已存在,会被替换,重置
52
+ :param toc: 类似fitz的table of contents,用一个n*3的表格表示新文档的格式
53
+ list,每个元素三列:标题级别,标题名称,(可选)对应文件内容
54
+ :param outline: 原来每份子文档里的标题,插入到新文档中的处理规则
55
+ demote:降级
56
+ clear:清除
57
+
58
+ 这个功能还有些局限性,后面要扩展鲁棒性
59
+ TODO 增加一个支持将原文档标题降级的功能,降到toc之后
60
+ """
61
+ app = XlWin32WordApplication.get_app()
62
+
63
+ master_doc = app.new_doc(master_file)
64
+ for item in toc:
65
+ lvl, title, file = item
66
+
67
+ # 1 加一个标题
68
+ r = master_doc.Paragraphs.Add().Range
69
+ r.InsertBefore(title)
70
+ r.Style = master_doc.Styles(f'标题 {lvl}')
71
+
72
+ # 2 拷贝完整的内容
73
+ if file:
74
+ file = XlPath(file)
75
+ member_doc = app.open_doc(file)
76
+ member_doc.Activate()
77
+
78
+ # 处理原来文档的目录级别
79
+ if outline == 'demote':
80
+ member_doc.outline_demote(lvl)
81
+ elif outline == 'clear':
82
+ # 降10级就相当于清除所有原来的标题
83
+ member_doc.outline_demote(10)
84
+
85
+ app.Selection.WholeStory()
86
+ app.Selection.Copy()
87
+ master_doc.Activate()
88
+ app.Selection.EndKey(Unit=app.wd('Story')) # 跳到文档末尾
89
+ app.Selection.Paste()
90
+ member_doc.Close()
91
+ master_doc.save()
92
+ master_doc.Close(True)
93
+
94
+
95
+ class Document:
96
+ """ 这个库写英文文档还不错。但不能做中文,字体会错乱。
97
+ """
98
+
99
+ def __init__(self, docx_file=None):
100
+ """
101
+ Args:
102
+ docx_file:
103
+ 已有的word文件路径:打开
104
+ 还没创建的word文件路径:在个别功能需要的时候,会自动创建
105
+ None:在临时文件夹生成一个默认的word文件
106
+ """
107
+ if docx_file is None:
108
+ self.docx_file = XlPath.tempfile('.docx')
109
+ else:
110
+ self.docx_file = XlPath(docx_file)
111
+ if self.docx_file:
112
+ self.doc = docx.Document(str(docx_file))
113
+ else:
114
+ self.doc = docx.Document()
115
+
116
+ def write(self):
117
+ XlPath(self.docx_file.parent).mkdir(exist_ok=True)
118
+ self.doc.save(str(self.docx_file))
119
+
120
+ def to_pdf(self, pdf_file=None):
121
+ self.write()
122
+ pdf_file = DocxTools.to_pdf(self.docx_file, pdf_file)
123
+ return pdf_file
124
+
125
+ def to_fitzdoc(self):
126
+ """ 获得 fitz的pdf文档对象
127
+ :return: FitzDoc对象
128
+ """
129
+ from pyxllib.file.pdflib import FitzDoc
130
+ pdf_file = self.to_pdf()
131
+ doc = FitzDoc(pdf_file)
132
+ return doc
133
+
134
+ def to_images(self, file_fmt='{filestem}_{number}.png', *args, scale=1, **kwargs):
135
+ doc = self.to_fitzdoc()
136
+ files = doc.to_images(doc.src_file.parent, file_fmt, *args, scale=scale, **kwargs)
137
+ return files
138
+
139
+ def browser(self):
140
+ """ 转pdf,使用浏览器的查看效果
141
+ """
142
+ pdf_file = self.to_pdf()
143
+ browser(pdf_file)
144
+
145
+ def display(self):
146
+ """ 转图片,使用jupyter环境的查看效果
147
+ """
148
+ from pyxllib.cv.expert import PilImg
149
+
150
+ # 转图片,并且裁剪,加边框输出
151
+ doc = self.to_fitzdoc()
152
+ for i in range(doc.page_count):
153
+ page = doc.load_page(i)
154
+ print('= ' * 10 + f' {page} ' + '= ' * 10)
155
+ img: PilImg = page.get_pil_image()
156
+ img.trim(border=5).plot_border().display()
157
+ del page
158
+
159
+ def to_labelmes(self, dst_dir=None, file_fmt='{filestem}_{number}.png', *, views=(0, 0, 1, 0), scale=1,
160
+ advance=False, indent=None):
161
+ """ 转labelme格式查看
162
+
163
+ 本质是把docx转成pdf,利用pdf的解析生成labelme格式的标准框查看
164
+
165
+ :param views: 详见to_labelmes的描述
166
+ 各位依次代表是否显示对应细粒度的标注:blocks、lines、spans、chars
167
+ :param bool|dict advance: 是否开启“高级”功能,开启后能获得下划线等属性,但速度会慢很多
168
+ 源生的fitz pdf解析是处理不了下划线的,开启高级功能后,有办法通过特殊手段实现下划线的解析
169
+ 默认会修正目前已知的下划线、颜色偏差问题
170
+ dict类型:以后功能多了,可能要支持自定义搭配,比如只复原unberline,但不管颜色偏差
171
+ """
172
+ from pyxlpr.data.labelme import LabelmeDict
173
+
174
+ # 1 转成图片,及json标注
175
+ doc = self.to_fitzdoc()
176
+ imfiles = doc.to_images(dst_dir, file_fmt, scale=scale)
177
+
178
+ # 2 高级功能
179
+ def is_color(x):
180
+ return x and sum(x)
181
+
182
+ def to_labelmes_advance():
183
+ m = 50 # 匹配run时,上文关联的文字长度,越长越严格
184
+
185
+ # 1 将带有下划线的run对象,使用特殊的hash规则存储起来
186
+ content = [] # 使用已遍历到的文本内容作为hash值
187
+ elements = {}
188
+ for p in self.paragraphs:
189
+ for r in p.runs:
190
+ # 要去掉空格,不然可能对不上。试过strip不行。分段会不太一样,还是把所有空格删了靠谱。
191
+ content.append(re.sub(r'\s+', '', r.text))
192
+ if r.underline or is_color(r.font.color.rgb): # 对有下划线、颜色的对象进行存储
193
+ # print(r.text + ',', r.underline, r.font.color.rgb, ''.join(content))
194
+ etag = get_etag(''.join(content)[-m:]) # 全部字符都判断的话太严格了,所以减小区间~
195
+ elements[etag] = r
196
+
197
+ # 2 检查json标注中为span的,其哈希规则是否有对应,则要单独设置扩展属性
198
+ content = ''
199
+ for i, file in enumerate(imfiles):
200
+ page = doc.load_page(i)
201
+ lmdict = LabelmeDict.gen_data(file)
202
+ lmdict['shapes'] = page.get_labelme_shapes('dict', views=views, scale=scale)
203
+
204
+ for sp in lmdict['shapes']:
205
+ attrs = DictTool.json_loads(sp['label'], 'label')
206
+ if attrs['category_name'] == 'span':
207
+ content += re.sub(r'\s+', '', attrs['text'])
208
+ etag = get_etag(content[-m:])
209
+ # print(content)
210
+ if etag in elements:
211
+ # print(content)
212
+ r = elements[etag] # 对应的原run对象
213
+ attrs = DictTool.json_loads(sp['label'])
214
+ x = r.underline
215
+ if x:
216
+ attrs['underline'] = int(x)
217
+ x = r.font.color.rgb
218
+ if is_color(x):
219
+ attrs['color'] = list(x)
220
+ sp['label'] = json.dumps(attrs)
221
+ file.with_suffix('.json').write(lmdict, indent=indent)
222
+
223
+ # 3 获得json
224
+ if advance:
225
+ to_labelmes_advance()
226
+ else:
227
+ doc.to_labelmes(imfiles, views=views, scale=scale)
228
+
229
+ def __getattr__(self, item):
230
+ # 属性:
231
+ # core_properties
232
+ # element
233
+ # inline_shapes
234
+ # paragraphs
235
+ # part
236
+ # sections
237
+ # settings
238
+ # styles
239
+ # tables
240
+ # 方法:
241
+ # add_heading
242
+ # add_page_break
243
+ # add_paragraph
244
+ # add_picture
245
+ # add_section
246
+ # add_table
247
+ # save
248
+ return getattr(self.doc, item)
249
+
250
+
251
+ class XlDocxTable(docx.table.Table):
252
+ def merge_samevalue_in_col(self, col, start_row=1):
253
+ """ 定义合并单元格的函数
254
+
255
+ :param col: 需要处理数据的列,0开始编号
256
+ :param start_row: 起始行,即表格中开始比对数据的行(其实标题排不排除一般无所谓~默认是排除了)
257
+ """
258
+
259
+ def merge_cells(start, end):
260
+ if end > start:
261
+ c = self.cell(start, col)
262
+ c.merge(self.cell(end, col))
263
+ c.text = self.cell(start, col).text.strip()
264
+ c.vertical_alignment = docx.enum.table.WD_ALIGN_VERTICAL.CENTER
265
+ c.paragraphs[0].alignment = docx.enum.text.WD_ALIGN_PARAGRAPH.CENTER
266
+
267
+ ref, start = None, start_row
268
+ for i in range(start_row, len(self.rows)):
269
+ v = self.cell(i, col).text
270
+ if v != ref:
271
+ merge_cells(start, i - 1)
272
+ ref, start = v, i
273
+ else:
274
+ self.cell(i, col).text = ''
275
+ merge_cells(start, i)
276
+
277
+
278
+ inject_members(XlDocxTable, docx.table.Table)
279
+
280
+
281
+ def __win32_word():
282
+ """ 使用win32com调用word
283
+
284
+ vba的文档:示例代码更多,vba语法也更熟悉,但显示的功能更不全
285
+ https://docs.microsoft.com/en-us/office/vba/api/word.saveas2
286
+ .net的文档:功能显示更全,应该是所有COM接口都有但示例代码更少、更不熟系
287
+ https://docs.microsoft.com/en-us/dotnet/api/microsoft.office.interop.word.documentclass.saveas2?view=word-pia
288
+ """
289
+ pass
290
+
291
+
292
+ @run_once
293
+ def inject_win32word(app, recursion_inject=False):
294
+ """ 给win32的word com接口添加功能
295
+
296
+ :param app: win32的类是临时生成的,需要给一个参考对象,才方便type(word)算出类型
297
+ :param recursion_inject: 是否递归,对目前有的各种子类扩展功能都绑定上
298
+ 默认关闭,如果影响到性能,可以关闭,后面运行中需要时手动设定inject注入
299
+ 开启,能方便业务层开发
300
+
301
+ 之前有想过可以生成doc里的时候再inject这些对象,但如果是批量处理脚本,每次建立doc都判断我觉得也麻烦
302
+ 长痛不如短痛,建立app的时候就把所有对象inject更方便
303
+ """
304
+ inject_members(XlWin32WordApplication, type(app), ignore_case=True)
305
+ if recursion_inject:
306
+ # 建一个临时文件,把各种需要绑定的对象都生成绑定一遍
307
+ # 确保初始化稍微慢点,但后面就方便了
308
+ doc = app.Documents.Add()
309
+ inject_members(XlWin32WordDocument, type(doc), ignore_case=True)
310
+
311
+ doc.Activate()
312
+ rng = doc.Range() # 全空的文档,有区间[0,1)
313
+ inject_members(XlWin32WordRange, type(rng), ignore_case=True)
314
+
315
+ doc.Hyperlinks.Add(rng, 'url') # 因为全空,这里会自动生成对应的明文url
316
+ inject_members(XlWin32WordHyperlink, type(doc.Hyperlinks(1)), ignore_case=True)
317
+
318
+ # 处理完关闭文档,不用保存
319
+ doc.Close(False)
320
+
321
+
322
+ class XlWin32WordApplication:
323
+ @classmethod
324
+ def get_app(cls, app=None, *, visible=None, display_alerts=0, recursion_inject=True):
325
+ """
326
+ Args:
327
+ app: 可以自定义在外部使用Dispatch、DispatchEx等形式给入已初始化好的app
328
+ visible: 是否可见
329
+ display_alerts: 是否关闭警告
330
+ recursion_inject: 是否递归执行inject
331
+ """
332
+ # 1 get app
333
+ name = 'WORD.APPLICATION'
334
+ if app is None:
335
+ try:
336
+ app = win32.GetActiveObject(name)
337
+ except pythoncom.com_error:
338
+ pass
339
+ if app is None:
340
+ try:
341
+ # 名称用大写,会比较兼容旧的word2013等版本
342
+ # 尽量静态调度,才能获得 from win32com.client import constants 的常量
343
+ app = win32.gencache.EnsureDispatch(name)
344
+ except TypeError:
345
+ # 实在不行,就用动态调度
346
+ app = win32.dynamic.Dispatch(name)
347
+ # 注:好像docx的默认打开程序也会有影响,最好默认都是office,不要被改成wps
348
+
349
+ # 2 inject
350
+ inject_win32word(app, recursion_inject=recursion_inject)
351
+
352
+ if visible is not None:
353
+ app.Visible = visible
354
+ if display_alerts is not None:
355
+ app.DisplayAlerts = display_alerts # 不警告
356
+
357
+ return app
358
+
359
+ def check_close(self, outfile):
360
+ """ 检查是否有指定名称的文件被打开,将其关闭,避免new_doc等操作出现问题
361
+ """
362
+ outfile = XlPath(outfile)
363
+ for x in self.Documents:
364
+ # 有可能文件来自onedrive,这里用safe_init更合理
365
+ if XlPath.init(x.Name, x.Path) == outfile:
366
+ x.Close()
367
+
368
+ def open_doc(self, file_name):
369
+ """ 打开已有的文件
370
+
371
+ 原傻逼底层接口,默认都是读取用户目录下的,我给改成默认打开当前工作目录的文件
372
+ 后文的save接口也是同理
373
+ """
374
+ doc = self.Documents.Open(str(XlPath.init(file_name, os.getcwd())))
375
+ return doc
376
+
377
+ def new_doc(self, file=None):
378
+ """ 创建一个新的文件
379
+ Args:
380
+ file: 文件路径
381
+ 空:新建一个doc,到时候保存会默认到临时文件夹
382
+ 不存在的文件名:新建对应的空文件
383
+ 已存在的文件名:重置、覆盖一个新的空文件
384
+
385
+ 使用该函数,会自动执行XlWin32WordDocument扩展。
386
+ """
387
+ if file is None:
388
+ file = XlPath.tempfile('.docx')
389
+ else:
390
+ file = XlPath(file)
391
+
392
+ doc = self.Documents.Add() # 创建新的word文档
393
+ doc.save(file)
394
+ return doc
395
+
396
+ @classmethod
397
+ def wd(cls, name, part=None):
398
+ """ 输入字符串名称,获得对应的常量值
399
+
400
+ :param name: 必须省略前缀wd。这个函数叫wd,就是帮忙省略掉前缀wd的意思
401
+ :param part: 特定组别的枚举值,可以输入特殊的name模式来取值
402
+ """
403
+ if part is None:
404
+ return getattr(constants, 'wd' + name)
405
+ else:
406
+ raise ValueError
407
+
408
+
409
+ class XlWin32WordDocument:
410
+ def save(self, file_name=None, fmt=None, retain=False, **kwargs):
411
+ """ 我自己简化的保存接口
412
+
413
+ :param file_name: 保存到指定路径,如果带有后缀
414
+ :param fmt: 毕竟是底层的com接口,不能做的太智能吧。连通过文件名后缀自动选择格式的功能都没有,要手动指定。
415
+ 为了方便,对这些进行智能自动处理,得到一个合理的save接口。
416
+ :param retain: SaveAs2的机制:如果目标格式仍是word支持的,则doc会切换到目标文件。否则doc保留原文件对象。
417
+ 这里retain若打开,则会自动做切换,保留原文件对象
418
+ :return: 跟retain有关,可能会"重置", outfile
419
+ 默认返回 outfile
420
+ 开启retain时,返回 outfile, doc
421
+ """
422
+
423
+ # 1 辅助函数
424
+ def save_format(fmt):
425
+ """ 枚举值映射,word保存类型的枚举
426
+
427
+ >> _('.html')
428
+ 8
429
+ >> _('Pdf')
430
+ 17
431
+ >> _('wdFormatFilteredHTML')
432
+ 10
433
+ """
434
+ # 复杂格式可能无法完美支持所有功能。比如复杂的pdf无法使用SaveAs2实现,要用ExportAsFixedFormat。
435
+ # 有些情况可能无法使用gencache,导致没有constants,所以默认可以直接映射到整数,避免使用到constants
436
+ common = {'doc': 0,
437
+ 'html': 8,
438
+ 'htm': 10, # 筛选过的html,一般文件会小的多
439
+ 'txt': 2,
440
+ 'docx': 16,
441
+ 'pdf': 17}
442
+ name = common.get(fmt.lower().lstrip('.'), fmt)
443
+ if isinstance(name, int):
444
+ return name
445
+ else:
446
+ return getattr(constants, 'wd' + name)
447
+
448
+ # 2 确认要存储的文件格式
449
+ if isinstance(fmt, str):
450
+ fmt = fmt.lower().lstrip('.')
451
+ elif file_name is not None:
452
+ fmt = XlPath(file_name).suffix[1:].lower()
453
+ elif self.Path:
454
+ fmt = os.path.splitext(self.Name)[1][1:].lower()
455
+ else:
456
+ fmt = 'docx'
457
+
458
+ # 3 保存一份原始的文件路径
459
+ origin_file = XlPath.init(self.Name, self.Path) if self.Path else None
460
+
461
+ # 4 如果有指定保存文件路径
462
+ if file_name is not None:
463
+ outfile = XlPath.init(file_name, os.getcwd())
464
+ if outfile.suffix[1:].lower() != fmt:
465
+ # 已有文件名,但这里指定的fmt不同于原文件,则认为是要另存为一个同名的不同格式文件
466
+ outfile = XlPath(outfile.stem, outfile.parent, suffix=fmt)
467
+ self.SaveAs2(str(outfile), save_format(fmt), **kwargs)
468
+ # 5 如果没指定保存文件路径
469
+ else:
470
+ if self.Path:
471
+ outfile = XlPath.init(self.Name, self.Path, suffix='.' + fmt)
472
+ self.SaveAs2(str(outfile), save_format(outfile.suffix), **kwargs)
473
+ else:
474
+ etag = get_etag(self.Content)
475
+ outfile = XlPath.init(etag, XlPath.tempdir(), suffix=fmt)
476
+ self.SaveAs2(str(outfile), save_format(fmt), **kwargs)
477
+
478
+ # 6 是否恢复原doc
479
+ cur_file = XlPath.init(self.Name, self.Path) # 当前文件不一定是目标文件f,如果是pdf等格式也不会切换过去
480
+ if retain and origin_file and origin_file != cur_file:
481
+ app = self.Application
482
+ self.Close()
483
+ self = app.open_doc(origin_file)
484
+
485
+ # 7 返回值
486
+ if retain:
487
+ return outfile, self
488
+ else:
489
+ return outfile
490
+
491
+ # 先暂时不开启 doc.chars
492
+ # @staticmethod
493
+ # def chars(doc):
494
+ # return doc.Range().chars
495
+
496
+ @property
497
+ def n_page(self):
498
+ return self.ComputeStatistics(2)
499
+ # return self.ActiveWindow.Panes(1).Pages.Count # 这样计算也行
500
+
501
+ def browser(self, file_name=None, fmt='html', retain=False):
502
+ """ 这个函数可能会导致原doc指向对象被销毁,建议要不追返回值doc继续使用
503
+ """
504
+ res = self.save(file_name, fmt, retain=retain)
505
+
506
+ if retain:
507
+ outfile, self = res
508
+ else:
509
+ outfile = res
510
+
511
+ browser(outfile)
512
+ return self
513
+
514
+ def add_section_size(self, factor=1):
515
+ """ 显示每节长度的标记
516
+ 一般在这里计算比在html计算方便
517
+ """
518
+ from humanfriendly import format_size
519
+
520
+ n = self.Paragraphs.Count
521
+ style_names, text_lens = [], []
522
+ for p in self.Paragraphs:
523
+ style_names.append(str(p.Style))
524
+ text_lens.append(strwidth(p.Range.Text))
525
+
526
+ for i, p in enumerate(self.Paragraphs):
527
+ name = style_names[i]
528
+ if name.startswith('标题'):
529
+ cumulate_size = 0
530
+ for j in range(i + 1, n):
531
+ if style_names[j] != name:
532
+ cumulate_size += text_lens[j - 1]
533
+ else:
534
+ break
535
+ if cumulate_size:
536
+ size = format_size(cumulate_size * factor).replace(' ', '').replace('bytes', 'B')
537
+ r = p.Range
538
+ self.Range(r.Start, r.End - 1).InsertAfter(f',{size}')
539
+
540
+ def outline_demote(self, demote_level):
541
+ """ 标题降级,降低level层 """
542
+ for p in self.Paragraphs:
543
+ p.Range.demote(demote_level)
544
+
545
+ def set_style(self, obj, name):
546
+ """ 给Paragraph、Range等按名称设置样式
547
+
548
+ :param obj: 当前doc下某个含有Style成员属性的子对象
549
+ :param name: 样式名称
550
+ """
551
+ setattr(obj, 'Style', self.Styles(name))
552
+
553
+ def add_paragraph(self, text='', style=None):
554
+ """ 自定义的插入段落
555
+
556
+ 默认的插入比较麻烦,新建段落、插入文本、设置格式要多步实现,这里封装支持在一步进行多种操作
557
+ """
558
+ p = self.Paragraphs.Add()
559
+ if text:
560
+ p.Range.InsertBefore(text)
561
+ if style:
562
+ p.Style = self.Styles(style)
563
+ return p
564
+
565
+
566
+ class XlWin32WordRange:
567
+ """ range是以下标0开始,左闭右开的区间
568
+
569
+ 当一个区间出现混合属性,比如有的有加粗,有的没加粗时,标记值为 app.wd('Undefined') 9999999
570
+ vba的True是值-1,False是值0
571
+ """
572
+
573
+ def set_hyperlink(self, url):
574
+ """ 给当前rng添加超链接
575
+ """
576
+ doc = self.Parent
577
+ doc.Hyperlinks.Add(self, url)
578
+
579
+ @property
580
+ def chars(self):
581
+ """
582
+
583
+ 注意,获得doc全部文本的其他常见方法
584
+ doc.Range().Text
585
+
586
+ """
587
+ # 有特殊换行,ch.Text可能会得到 '\r\x07',为了位置对应,只记录一个字符
588
+ return ''.join([ch.Text[0] for ch in self.Characters])
589
+
590
+ def char_range(self, start=0, end=None):
591
+ """ 定位rng中的子range对象,这里是以可见字符Characters计数的
592
+
593
+ :param start: 下标类似切片的规则
594
+ :param end: 见start描述,允许越界,允许负数
595
+ 默认不输入表示匹配到末尾
596
+ """
597
+ n = self.Characters.Count
598
+ if end is None or end > n:
599
+ end = n
600
+ elif end < 0:
601
+ end = n + end
602
+ start_idx, end_idx = self.Characters(start + 1).Start, self.Characters(end).End
603
+ return self.Document.Range(start_idx, end_idx)
604
+
605
+ def shifting(self, left=0, right=0):
606
+ """ range左右两边增加偏移量,返回重定位的rng
607
+
608
+ 常用语段落定位,要在段落末尾增加内容时
609
+ >> rng2 = p.Range.shifting(right=-1)
610
+ """
611
+ return self.Document.Range(self.Start + left, self.End + right)
612
+
613
+ def demote(self, demote_level):
614
+ """ 标题降级,降低level层 """
615
+ name = self.Style.NameLocal # 获得样式名称
616
+ m = re.match(r'标题 (\d)$', name)
617
+ if m:
618
+ lvl = int(m.group(1))
619
+ new_lvl = lvl + demote_level
620
+ new_style = f'标题 {new_lvl}' if new_lvl < 10 else '正文'
621
+ self.Style = self.Parent.Styles(new_style)
622
+
623
+ def set_font(self, font_fmt):
624
+ """ 设置字体各种格式
625
+
626
+ :param dict|str font_fmt:
627
+ dict,定制格式
628
+ 布尔类型:Bold、Italic、Subscript、Superscript
629
+ 可布尔的值类型:Underline
630
+ 支持的格式见:https://docs.microsoft.com/en-us/office/vba/api/word.wdunderline
631
+ 值类型:Name、Size、Color、UnderlineColor
632
+ str,使用现有样式名
633
+ """
634
+ if isinstance(font_fmt, dict):
635
+ font = self.Font
636
+ for k, v in font_fmt.items():
637
+ setattr(font, k, v)
638
+ elif isinstance(font_fmt, str):
639
+ self.Style = self.Parent.Styles(font_fmt)
640
+ else:
641
+ raise ValueError
642
+
643
+ def insert_before(self, text, font_fmt=None):
644
+ """ 对原InsertBefore的功能封装
645
+
646
+ :return: 增加返回值,是新插入内容的range定位
647
+ """
648
+ start1, end1 = self.Start, self.End
649
+ self.InsertBefore(text)
650
+ bias = self.End - end1 # 新插入的内容大小
651
+ new_rng = self.Document.Range(start1, start1 + bias)
652
+ if font_fmt:
653
+ new_rng.set_font(font_fmt)
654
+ return new_rng
655
+
656
+ def insert_after(self, text, font_fmt=None):
657
+ """ 同insert_before,是InsertAfter的重封装
658
+ """
659
+ # 1
660
+ start1, end1 = self.Start, self.End
661
+
662
+ # 2 往后插入,会排除\r情况
663
+ doc = self.Document
664
+ ch = doc.Range(end1 - 1, end1).Text
665
+ if ch == '\r':
666
+ end1 -= 1
667
+ self = doc.Range(start1, end1)
668
+
669
+ # 3
670
+ self.InsertAfter(text)
671
+ bias = self.End - end1
672
+ new_rng = self.Document.Range(end1, end1 + bias)
673
+ if font_fmt:
674
+ new_rng.set_font(font_fmt)
675
+ return new_rng
676
+
677
+
678
+ class XlWin32WordHyperlink:
679
+ @property
680
+ def netloc(self):
681
+ from urllib.parse import urlparse
682
+ linkp = urlparse(self.Name) # 链接格式解析
683
+ # netloc = linkp.netloc or Path(linkp.path).name
684
+ netloc = linkp.netloc or linkp.scheme # 可能是本地文件,此时记录其所在磁盘
685
+ return netloc
686
+
687
+ @property
688
+ def name(self):
689
+ """ 这个是转成明文的完整链接,如果要编码过的,可以取link.Name """
690
+ from urllib.parse import unquote
691
+ return unquote(self.Name)
692
+
693
+
694
+ def rebuild_document_by_word(fmt='html', translate=False, navigation=False, visible=False, quit=None):
695
+ """ 将剪切板的内容粘贴到word重新排版,再转成fmt格式的文档,用浏览器打开
696
+
697
+ 这个功能只能在windows平台使用,并且必须要安装有Word软件。
698
+
699
+ 一般用于英文网站,生成双语阅读的模板,再调用谷歌翻译。
700
+ 生成的文档如果有需要,一般是手动复制整理到另一个docx文件中。
701
+
702
+ Args:
703
+ fmt: 输出文件类型
704
+ 常见的可以简写:html、pdf、txt
705
+ 其他特殊需求可以用word原变量名:wdFormatDocument
706
+ visible: 是否展示运行过程,如果不展示,默认最后会close文档
707
+ quit: 运行完是否退出应用
708
+ translate: html专用业务功能,表示是否对p拷贝一份notranslate的对象,用于谷歌翻译双语对照
709
+ navigation: 是否增加导航栏
710
+ 注意,使用导航栏后,页面就无法直接使用谷歌翻译了
711
+ 但可以自己进入_content文件,使用谷歌翻译处理,自覆盖保存
712
+ 然后再回到_index文件,刷新即可
713
+ """
714
+ import pyperclip
715
+ from pyxllib.text.xmllib import BeautifulSoup, html_bitran_template, MakeHtmlNavigation
716
+
717
+ # 1 保存的临时文件名采用etag
718
+ f = XlPath.init(get_etag(pyperclip.paste()), XlPath.tempdir(), suffix=fmt)
719
+ app = XlWin32WordApplication.get_app(visible=visible)
720
+ app.check_close(f)
721
+ doc = app.new_doc(f)
722
+ doc.Activate()
723
+ app.Selection.Paste()
724
+
725
+ # 2 如果需要,也可以在这个阶段,插入word自动化的操作,而不是后续在html层面操作
726
+ # 统计每节内容长度,每个字母1B,每个汉字2B
727
+ doc.add_section_size()
728
+ file = doc.save(f, fmt)
729
+ doc.Close()
730
+
731
+ # 3 html格式扩展功能
732
+ if fmt == 'html':
733
+ # 3.1 默认扩展功能
734
+ s = file.read_text(encoding='gbk')
735
+ # s = s.replace('\xa0', '') # 不知道这样去除\xa0行不行,等下次遇到尝试
736
+ bs = BeautifulSoup(s, 'lxml')
737
+ bs.head_add_number() # 给标题加上编号
738
+ # bs.head_add_size() # 显示每节内容长短
739
+ content = str(bs)
740
+
741
+ # TODO 识别微信、pydoc,然后做一些自动化清理?
742
+ # TODO 过度缩进问题?
743
+
744
+ # 3.2 双语对照阅读
745
+ if translate:
746
+ # word生成的html固定是gbk编码
747
+ content = html_bitran_template(content)
748
+
749
+ # 原文是gbk,但到谷歌默认是utf8,所以改一改
750
+ # 这样改后问题是word可能又反而有问题了,不过word本来只是跳板,并不是要用word编辑html
751
+ file.write_text(content, encoding='utf8')
752
+
753
+ # 3.3 导航栏功能
754
+ # 作为临时使用可以开,如果要复制到word,并没有必要
755
+ if navigation:
756
+ file = MakeHtmlNavigation.from_file(file, encoding='utf8', number=False, text_catalogue=False)
757
+
758
+ if quit:
759
+ app.Quit()
760
+
761
+ return file