xl-docx 0.6.7__tar.gz

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 (34) hide show
  1. xl_docx-0.6.7/.gitignore +3 -0
  2. xl_docx-0.6.7/PKG-INFO +14 -0
  3. xl_docx-0.6.7/README.md +0 -0
  4. xl_docx-0.6.7/assets/h.docx +0 -0
  5. xl_docx-0.6.7/assets/img/word-template.png +0 -0
  6. xl_docx-0.6.7/assets/img/wordx-tool.png +0 -0
  7. xl_docx-0.6.7/assets/v.docx +0 -0
  8. xl_docx-0.6.7/pyproject.toml +27 -0
  9. xl_docx-0.6.7/src/xl_word/__init__.py +4 -0
  10. xl_docx-0.6.7/src/xl_word/__main__.py +5 -0
  11. xl_docx-0.6.7/src/xl_word/compiler/__init__.py +79 -0
  12. xl_docx-0.6.7/src/xl_word/compiler/processors/__init__.py +6 -0
  13. xl_docx-0.6.7/src/xl_word/compiler/processors/base.py +73 -0
  14. xl_docx-0.6.7/src/xl_word/compiler/processors/directive.py +75 -0
  15. xl_docx-0.6.7/src/xl_word/compiler/processors/pager.py +196 -0
  16. xl_docx-0.6.7/src/xl_word/compiler/processors/paragraph.py +147 -0
  17. xl_docx-0.6.7/src/xl_word/compiler/processors/style.py +70 -0
  18. xl_docx-0.6.7/src/xl_word/compiler/processors/table.py +246 -0
  19. xl_docx-0.6.7/src/xl_word/document.py +87 -0
  20. xl_docx-0.6.7/src/xl_word/h.docx +0 -0
  21. xl_docx-0.6.7/src/xl_word/mixins.py +102 -0
  22. xl_docx-0.6.7/src/xl_word/sheet.py +152 -0
  23. xl_docx-0.6.7/src/xl_word/tool/__init__.py +333 -0
  24. xl_docx-0.6.7/src/xl_word/tool/build.bat +27 -0
  25. xl_docx-0.6.7/src/xl_word/tool/document.xml +0 -0
  26. xl_docx-0.6.7/src/xl_word/tool/gui.py +120 -0
  27. xl_docx-0.6.7/src/xl_word/tool/h.docx +0 -0
  28. xl_docx-0.6.7/src/xl_word/tool/tool.spec +44 -0
  29. xl_docx-0.6.7/src/xl_word/tool/utility.py +150 -0
  30. xl_docx-0.6.7/src/xl_word/tool/v.docx +0 -0
  31. xl_docx-0.6.7/src/xl_word/utils/fake_zip.py +138 -0
  32. xl_docx-0.6.7/src/xl_word/utils/tree.py +56 -0
  33. xl_docx-0.6.7/src/xl_word/v.docx +0 -0
  34. xl_docx-0.6.7/src/xl_word/word_file.py +57 -0
@@ -0,0 +1,3 @@
1
+ dist
2
+ build
3
+
xl_docx-0.6.7/PKG-INFO ADDED
@@ -0,0 +1,14 @@
1
+ Metadata-Version: 2.1
2
+ Name: xl-docx
3
+ Version: 0.6.7
4
+ Summary: generate word documents in a sexy way
5
+ Project-URL: Homepage, https://github.com/inspirare6/xl-docx
6
+ Project-URL: Bug Tracker, https://github.com/inspirare6/xl-docx/issues
7
+ Author-email: inspirare6 <inspirare6@163.com>
8
+ Classifier: License :: OSI Approved :: MIT License
9
+ Classifier: Operating System :: OS Independent
10
+ Classifier: Programming Language :: Python :: 3
11
+ Requires-Python: >=3.7
12
+ Requires-Dist: cssselect==1.2.0
13
+ Requires-Dist: jinja2==3.1.2
14
+ Requires-Dist: lxml==4.9.2
File without changes
Binary file
Binary file
Binary file
@@ -0,0 +1,27 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "xl-docx"
7
+ version = "0.6.7"
8
+ authors = [
9
+ { name="inspirare6", email="inspirare6@163.com" },
10
+ ]
11
+ description = "generate word documents in a sexy way"
12
+ readme = "README.md"
13
+ requires-python = ">=3.7"
14
+ dependencies = [
15
+ "jinja2==3.1.2",
16
+ "lxml==4.9.2",
17
+ "cssselect==1.2.0"
18
+ ]
19
+ classifiers = [
20
+ "Programming Language :: Python :: 3",
21
+ "License :: OSI Approved :: MIT License",
22
+ "Operating System :: OS Independent",
23
+ ]
24
+
25
+ [project.urls]
26
+ "Homepage" = "https://github.com/inspirare6/xl-docx"
27
+ "Bug Tracker" = "https://github.com/inspirare6/xl-docx/issues"
@@ -0,0 +1,4 @@
1
+ from xl_word.utils.fake_zip import FakeZip
2
+ from xl_word.word_file import WordFile
3
+ from xl_word.sheet import Sheet
4
+ from xl_word.document import Document
@@ -0,0 +1,5 @@
1
+ from xl_word.tool import WordTemplateEditor
2
+
3
+
4
+ editor = WordTemplateEditor()
5
+ editor.run()
@@ -0,0 +1,79 @@
1
+ from jinja2 import Environment, BaseLoader, TemplateNotFound
2
+ from typing import Dict, Any
3
+ from xl_word.compiler.processors import BaseProcessor, StyleProcessor, DirectiveProcessor, \
4
+ TableProcessor, ParagraphProcessor, PagerProcessor
5
+
6
+
7
+ class ImageProcessor(BaseProcessor):
8
+ """处理图片相关的XML标签"""
9
+ def compile(self, xml: str) -> str:
10
+ def process_image(match):
11
+ attrs = self._extract_attrs(match.group(0), ['src', 'width', 'height'])
12
+ rid = attrs['src']
13
+ width = attrs['width']
14
+ height = attrs['height']
15
+
16
+ return f'''<w:pict>
17
+ <v:shape style="width:{width}px;height:{height}px">
18
+ <v:imagedata r:id="{rid}"/>
19
+ </v:shape>
20
+ </w:pict>'''
21
+
22
+ return self._process_tag(xml, r'<xl-img[^>]+/>', process_image)
23
+
24
+
25
+ class XMLTemplateLoader(BaseLoader):
26
+ def __init__(self, template_str: str):
27
+ self.template_str = template_str
28
+
29
+ def get_source(self, environment: Environment, template: str) -> tuple:
30
+ if template == 'root':
31
+ return self.template_str, None, lambda: True
32
+ raise TemplateNotFound(template)
33
+
34
+ class XMLCompiler:
35
+ """XML编译器主类"""
36
+ def __init__(self):
37
+ self.processors = [
38
+ StyleProcessor(),
39
+ DirectiveProcessor(),
40
+ TableProcessor(),
41
+ ParagraphProcessor(),
42
+ ImageProcessor(),
43
+ PagerProcessor()
44
+ ]
45
+ self.env = Environment(
46
+ loader=XMLTemplateLoader(""),
47
+ autoescape=False,
48
+ trim_blocks=True,
49
+ lstrip_blocks=True
50
+ )
51
+
52
+ def compile_template(self, template: str) -> str:
53
+ for processor in self.processors:
54
+ if hasattr(processor, 'compile'):
55
+ template = processor.compile(template)
56
+
57
+ return template
58
+
59
+ def decompile_template(self, template: str) -> str:
60
+ for processor in self.processors:
61
+ if hasattr(processor, 'decompile'):
62
+ template = processor.decompile(template)
63
+
64
+ return template
65
+
66
+
67
+ def render_template(self, template: str, data: Dict[str, Any]) -> str:
68
+ processed_template = self.compile_template(template)
69
+ env = Environment(
70
+ loader=XMLTemplateLoader(processed_template),
71
+ autoescape=False,
72
+ trim_blocks=True,
73
+ lstrip_blocks=True
74
+ )
75
+
76
+ template = env.get_template('root')
77
+ result = template.render(**data)
78
+
79
+ return result
@@ -0,0 +1,6 @@
1
+ from xl_word.compiler.processors.base import BaseProcessor
2
+ from xl_word.compiler.processors.style import StyleProcessor
3
+ from xl_word.compiler.processors.directive import DirectiveProcessor
4
+ from xl_word.compiler.processors.table import TableProcessor
5
+ from xl_word.compiler.processors.paragraph import ParagraphProcessor
6
+ from xl_word.compiler.processors.pager import PagerProcessor
@@ -0,0 +1,73 @@
1
+ import re
2
+
3
+
4
+ class BaseProcessor:
5
+ """XML处理器基类"""
6
+ @classmethod
7
+ def compile(cls, xml):
8
+ raise NotImplementedError
9
+
10
+ @staticmethod
11
+ def retrieve(dict_, keys):
12
+ """字典解构赋值
13
+ params = {'a': 1, 'b': 2}
14
+ a, b = get(params, ['a', 'b'])
15
+ a, c = get(params, ['a', 'c'])
16
+ """
17
+ tmp = ()
18
+ for key in keys:
19
+ tmp += (dict_.get(key),)
20
+ return tmp
21
+
22
+ @classmethod
23
+ def _process_tag(cls, xml, pattern, process_func):
24
+ """通用标签处理方法"""
25
+ return re.sub(pattern, process_func, xml, flags=re.DOTALL)
26
+
27
+ @classmethod
28
+ def _extract_attrs(cls, attrs_str, attr_names):
29
+ """提取属性值"""
30
+ result = []
31
+ for name in attr_names:
32
+ match = re.search(f'{name}="([^"]*)"', attrs_str)
33
+ result.append(match.group(1) if match else None)
34
+ return tuple(result)
35
+
36
+ @classmethod
37
+ def _get_value(cls, pattern, xml):
38
+ match = re.search(pattern, xml)
39
+ return match.group(1) if match else None
40
+
41
+ @classmethod
42
+ def _build_props(cls, props, indent=''):
43
+ """构建属性字符串"""
44
+ if not props:
45
+ return ''
46
+ return f'\n{indent}' + f'\n{indent}'.join(props) + f'\n{indent}'
47
+
48
+ @classmethod
49
+ def _parse_style_str(cls, style_str):
50
+ """解析样式字符串为字典
51
+ 例如: "font-size:12px;color:red" -> {"font-size": "12px", "color": "red"}
52
+ """
53
+ if not style_str:
54
+ return {}
55
+ return dict(pair.split(':') for pair in style_str.split(';') if pair.strip())
56
+
57
+ @classmethod
58
+ def _build_style_str(cls, styles):
59
+ """将样式字典转换为字符串
60
+ 例如: {"font-size": "12px", "color": "red"} -> "font-size:12px;color:red"
61
+ """
62
+ if not styles:
63
+ return ''
64
+ return ';'.join(f"{k}:{v}" for k, v in styles.items())
65
+
66
+ @classmethod
67
+ def _build_attr_str(cls, attrs):
68
+ """将属性字典转换为字符串
69
+ 例如: {"font-size": "12px", "color": "red"} -> "font-size:12px;color:red"
70
+ """
71
+ if not attrs:
72
+ return ''
73
+ return ' '.join(f"{k}='{v}'" for k, v in attrs.items())
@@ -0,0 +1,75 @@
1
+ from xl_word.compiler.processors.base import BaseProcessor
2
+ import re
3
+
4
+
5
+ class DirectiveProcessor(BaseProcessor):
6
+ """处理Vue指令相关的XML标签"""
7
+ @classmethod
8
+ def compile(cls, xml: str) -> str:
9
+ xml = cls._process_v_if(xml)
10
+ xml = cls._process_v_for(xml)
11
+ return xml
12
+
13
+ @classmethod
14
+ def _process_v_if(cls, xml: str) -> str:
15
+ def process_if(match):
16
+ tag_name, condition, remaining_attrs = match.groups()
17
+ close_tag_pattern = f'</\s*{tag_name}\s*>'
18
+ close_match = re.search(close_tag_pattern, xml[match.end():])
19
+ if close_match:
20
+ content_between = xml[match.end():match.end() + close_match.start()]
21
+ return f'{{% if {condition} %}}<{tag_name}{remaining_attrs}>{content_between}</{tag_name}>{{% endif %}}'
22
+ return match.group(0)
23
+
24
+ return cls._process_tag(xml, r'<([^>]*)\s+v-if="([^"]*)"([^>]*)>', process_if)
25
+
26
+ @classmethod
27
+ def _process_v_for(cls, xml: str) -> str:
28
+ def process_for(match):
29
+ tag_name, loop_expr, remaining_attrs = match.groups()
30
+ item, items = loop_expr.split(' in ')
31
+ item = item.strip()
32
+ items = items.strip()
33
+
34
+ close_tag_pattern = f'</\s*{tag_name}\s*>'
35
+ close_match = re.search(close_tag_pattern, xml[match.end():])
36
+ if close_match:
37
+ content_between = xml[match.end():match.end() + close_match.start()]
38
+ return f'{{% for {item} in {items} %}}<{tag_name}{remaining_attrs}>{content_between}</{tag_name}>{{% endfor %}}'
39
+ return match.group(0)
40
+
41
+ return cls._process_tag(xml, r'<([^>]*)\s+v-for="([^"]*)"([^>]*)>', process_for)
42
+
43
+ @classmethod
44
+ def decompile2222222222222222222222(cls, xml: str) -> str:
45
+ """将Jinja2模板转换回Vue指令"""
46
+ xml = cls._decompile_v_if(xml)
47
+ xml = cls._decompile_v_for(xml)
48
+ return xml
49
+
50
+ @classmethod
51
+ def _decompile_v_if(cls, xml: str) -> str:
52
+ def process_if(match):
53
+ condition, tag_content = match.groups()
54
+ # 提取标签名和属性
55
+ tag_match = re.match(r'<([^\s>]+)([^>]*)>(.*)</\1>', tag_content)
56
+ if tag_match:
57
+ tag_name, attrs, content = tag_match.groups()
58
+ return f'<{tag_name} v-if="{condition}"{attrs}>{content}</{tag_name}>'
59
+ return match.group(0)
60
+
61
+ return cls._process_tag(xml, r'{%\s*if\s+([^%]+)\s*%}(.*?){%\s*endif\s*%}', process_if)
62
+
63
+ @classmethod
64
+ def _decompile_v_for(cls, xml: str) -> str:
65
+ def process_for(match):
66
+ loop_expr, tag_content = match.groups()
67
+ # 提取标签名和属性
68
+ tag_match = re.match(r'<([^\s>]+)([^>]*)>(.*)</\1>', tag_content)
69
+ if tag_match:
70
+ tag_name, attrs, content = tag_match.groups()
71
+ item, items = loop_expr.split(' in ')
72
+ return f'<{tag_name} v-for="{item.strip()} in {items.strip()}"{attrs}>{content}</{tag_name}>'
73
+ return match.group(0)
74
+
75
+ return cls._process_tag(xml, r'{%\s*for\s+([^%]+)\s*%}(.*?){%\s*endfor\s*%}', process_for)
@@ -0,0 +1,196 @@
1
+ from xl_word.compiler.processors.base import BaseProcessor
2
+ import re
3
+
4
+
5
+ class PagerProcessor(BaseProcessor):
6
+ """处理页码相关的XML标签"""
7
+ def compile(self, xml: str) -> str:
8
+
9
+ def process_pager(match):
10
+ style, = self._extract_attrs(match.group(0), ['style'])
11
+ styles = self._parse_style_str(style)
12
+
13
+ font_size = styles.get('font-size', '21')
14
+ english_font = styles.get('english', 'Times New Roman')
15
+ chinese_font = styles.get('chinese', 'SimSun')
16
+
17
+ return f'''<w:sdt>
18
+ <w:sdtPr>
19
+ <w:id w:val="98381352"/>
20
+ <w:docPartObj>
21
+ <w:docPartGallery w:val="Page Numbers (Top of Page)"/>
22
+ <w:docPartUnique/>
23
+ </w:docPartObj>
24
+ </w:sdtPr>
25
+ <w:sdtEndPr>
26
+ <w:rPr>
27
+ <w:rFonts w:ascii="{english_font}" w:hAnsi="{chinese_font}"/>
28
+ <w:szCs w:val="{font_size}"/>
29
+ </w:rPr>
30
+ </w:sdtEndPr>
31
+ <w:sdtContent>
32
+ <w:r>
33
+ <w:rPr>
34
+ <w:rFonts w:ascii="{english_font}" w:hAnsi="宋体" w:hint="eastAsia"/>
35
+ <w:szCs w:val="{font_size}"/>
36
+ </w:rPr>
37
+ <w:t>第</w:t>
38
+ </w:r>
39
+ <w:r>
40
+ <w:rPr>
41
+ <w:rFonts w:ascii="{english_font}" w:hAnsi="{chinese_font}"/>
42
+ <w:szCs w:val="{font_size}"/>
43
+ <w:lang w:val="zh-CN"/>
44
+ </w:rPr>
45
+ <w:t xml:space="preserve"> </w:t>
46
+ </w:r>
47
+ <w:r>
48
+ <w:rPr>
49
+ <w:rFonts w:ascii="{english_font}" w:hAnsi="{chinese_font}"/>
50
+ <w:b/>
51
+ <w:bCs/>
52
+ <w:szCs w:val="{font_size}"/>
53
+ </w:rPr>
54
+ <w:fldChar w:fldCharType="begin"/>
55
+ </w:r>
56
+ <w:r>
57
+ <w:rPr>
58
+ <w:rFonts w:ascii="{english_font}" w:hAnsi="{chinese_font}"/>
59
+ <w:b/>
60
+ <w:bCs/>
61
+ <w:szCs w:val="{font_size}"/>
62
+ </w:rPr>
63
+ <w:instrText>PAGE</w:instrText>
64
+ </w:r>
65
+ <w:r>
66
+ <w:rPr>
67
+ <w:rFonts w:ascii="{english_font}" w:hAnsi="{chinese_font}"/>
68
+ <w:b/>
69
+ <w:bCs/>
70
+ <w:szCs w:val="{font_size}"/>
71
+ </w:rPr>
72
+ <w:fldChar w:fldCharType="separate"/>
73
+ </w:r>
74
+ <w:r>
75
+ <w:rPr>
76
+ <w:rFonts w:ascii="{english_font}" w:hAnsi="{chinese_font}"/>
77
+ <w:b/>
78
+ <w:bCs/>
79
+ <w:noProof/>
80
+ <w:szCs w:val="{font_size}"/>
81
+ </w:rPr>
82
+ <w:t>2</w:t>
83
+ </w:r>
84
+ <w:r>
85
+ <w:rPr>
86
+ <w:rFonts w:ascii="{english_font}" w:hAnsi="{chinese_font}"/>
87
+ <w:b/>
88
+ <w:bCs/>
89
+ <w:szCs w:val="{font_size}"/>
90
+ </w:rPr>
91
+ <w:fldChar w:fldCharType="end"/>
92
+ </w:r>
93
+ <w:r>
94
+ <w:rPr>
95
+ <w:rFonts w:ascii="{english_font}" w:hAnsi="{chinese_font}"/>
96
+ <w:szCs w:val="{font_size}"/>
97
+ <w:lang w:val="zh-CN"/>
98
+ </w:rPr>
99
+ <w:t xml:space="preserve"> </w:t>
100
+ </w:r>
101
+ <w:r>
102
+ <w:rPr>
103
+ <w:rFonts w:ascii="{english_font}" w:hAnsi="宋体" w:hint="eastAsia"/>
104
+ <w:szCs w:val="{font_size}"/>
105
+ <w:lang w:val="zh-CN"/>
106
+ </w:rPr>
107
+ <w:t>页</w:t>
108
+ </w:r>
109
+ <w:r>
110
+ <w:rPr>
111
+ <w:rFonts w:ascii="{english_font}" w:hAnsi="{chinese_font}"/>
112
+ <w:szCs w:val="{font_size}"/>
113
+ <w:lang w:val="zh-CN"/>
114
+ </w:rPr>
115
+ <w:t xml:space="preserve"> </w:t>
116
+ </w:r>
117
+ <w:r>
118
+ <w:rPr>
119
+ <w:rFonts w:ascii="{english_font}" w:hAnsi="宋体" w:hint="eastAsia"/>
120
+ <w:szCs w:val="{font_size}"/>
121
+ <w:lang w:val="zh-CN"/>
122
+ </w:rPr>
123
+ <w:t>共</w:t>
124
+ </w:r>
125
+ <w:r>
126
+ <w:rPr>
127
+ <w:rFonts w:ascii="{english_font}" w:hAnsi="{chinese_font}"/>
128
+ <w:szCs w:val="{font_size}"/>
129
+ <w:lang w:val="zh-CN"/>
130
+ </w:rPr>
131
+ <w:t xml:space="preserve"> </w:t>
132
+ </w:r>
133
+ <w:r>
134
+ <w:rPr>
135
+ <w:rFonts w:ascii="{english_font}" w:hAnsi="{chinese_font}"/>
136
+ <w:b/>
137
+ <w:bCs/>
138
+ <w:szCs w:val="{font_size}"/>
139
+ </w:rPr>
140
+ <w:fldChar w:fldCharType="begin"/>
141
+ </w:r>
142
+ <w:r>
143
+ <w:rPr>
144
+ <w:rFonts w:ascii="{english_font}" w:hAnsi="{chinese_font}"/>
145
+ <w:b/>
146
+ <w:bCs/>
147
+ <w:szCs w:val="{font_size}"/>
148
+ </w:rPr>
149
+ <w:instrText>NUMPAGES</w:instrText>
150
+ </w:r>
151
+ <w:r>
152
+ <w:rPr>
153
+ <w:rFonts w:ascii="{english_font}" w:hAnsi="{chinese_font}"/>
154
+ <w:b/>
155
+ <w:bCs/>
156
+ <w:szCs w:val="{font_size}"/>
157
+ </w:rPr>
158
+ <w:fldChar w:fldCharType="separate"/>
159
+ </w:r>
160
+ <w:r>
161
+ <w:rPr>
162
+ <w:rFonts w:ascii="{english_font}" w:hAnsi="{chinese_font}"/>
163
+ <w:b/>
164
+ <w:bCs/>
165
+ <w:noProof/>
166
+ <w:szCs w:val="{font_size}"/>
167
+ </w:rPr>
168
+ <w:t>2</w:t>
169
+ </w:r>
170
+ <w:r>
171
+ <w:rPr>
172
+ <w:rFonts w:ascii="{english_font}" w:hAnsi="{chinese_font}"/>
173
+ <w:b/>
174
+ <w:bCs/>
175
+ <w:szCs w:val="{font_size}"/>
176
+ </w:rPr>
177
+ <w:fldChar w:fldCharType="end"/>
178
+ </w:r>
179
+ <w:r>
180
+ <w:rPr>
181
+ <w:rFonts w:ascii="{english_font}" w:hAnsi="{chinese_font}"/>
182
+ <w:szCs w:val="{font_size}"/>
183
+ </w:rPr>
184
+ <w:t xml:space="preserve"> </w:t>
185
+ </w:r>
186
+ <w:r>
187
+ <w:rPr>
188
+ <w:rFonts w:ascii="{english_font}" w:hAnsi="宋体" w:hint="eastAsia"/>
189
+ <w:szCs w:val="{font_size}"/>
190
+ </w:rPr>
191
+ <w:t>页</w:t>
192
+ </w:r>
193
+ </w:sdtContent>
194
+ </w:sdt>'''
195
+
196
+ return self._process_tag(xml, r'<xl-pager.*?/>', process_pager)
@@ -0,0 +1,147 @@
1
+ from xl_word.compiler.processors.base import BaseProcessor
2
+ import re
3
+
4
+
5
+ class ParagraphProcessor(BaseProcessor):
6
+ """处理段落相关的XML标签"""
7
+ @classmethod
8
+ def compile(cls, xml: str) -> str:
9
+ """将xl-p标签转换为w:p标签"""
10
+ def process_paragraph(match):
11
+ style_str = match.group(1) or ''
12
+ content = match.group(2).strip()
13
+ styles = cls._parse_style_str(style_str)
14
+
15
+ p_props_str = '<w:pPr>'
16
+ align, padding_top, padding_bottom, english, chinese, font_size, font_weight = cls.retrieve(styles, \
17
+ ['align', 'padding-top', 'padding-bottom', 'english', 'chinese', 'font-size', 'font-weight'])
18
+ p_props_str += f'<w:jc w:val="{align}"/>' if align else ''
19
+ spacing_attr_str = ''
20
+ spacing_attr_str += f'w:before="{padding_top}" ' if padding_top else ''
21
+ spacing_attr_str += f'w:after="{padding_bottom}"' if padding_bottom else ''
22
+ p_props_str += f'<w:spacing {spacing_attr_str}/>' if spacing_attr_str else ''
23
+ p_props_str += '</w:pPr>'
24
+
25
+ r_props_str = '<w:rPr>'
26
+ r_props_str += f'<w:rFonts w:ascii="{english}" w:cs="{chinese}" w:eastAsia="{english}" w:hAnsi="{english}" w:hint="eastAsia"/>' if (english and chinese) else ''
27
+ r_props_str += f'<w:kern w:val="0"/>' if font_size else ''
28
+ r_props_str += f'<w:sz w:val="{font_size}"/>' if font_size else ''
29
+ r_props_str += f'<w:szCs w:val="{font_size}"/>' if font_size else ''
30
+ r_props_str += f'<w:b/>' if font_weight == 'bold' else ''
31
+ r_props_str += '</w:rPr>'
32
+
33
+ def process_span(match):
34
+ style_str = match.group(1) or ''
35
+ content = match.group(2).strip()
36
+ styles = cls._parse_style_str(style_str)
37
+ underline, font_size, font_weight = cls.retrieve(styles, ['underline', 'font-size', 'font-weight'])
38
+ r_props_str_ = r_props_str
39
+ r_props_str_inner = f'<w:u w:val="{underline}"/>' if underline else ''
40
+ r_props_str_inner += f'<w:sz w:val="{font_size}"/>' if font_size else ''
41
+ r_props_str_inner += f'<w:b/>' if font_weight == 'bold' else ''
42
+ r_props_str_inner += '</w:rPr>'
43
+ r_props_str_inner = r_props_str_.replace('</w:rPr>', r_props_str_inner)
44
+
45
+ return f'<w:r>{r_props_str_inner}<w:t xml:space="preserve">{content}</w:t></w:r>'
46
+
47
+ def process_signature(match):
48
+ data_var, height = match.groups()
49
+ height = height.replace('px', '')
50
+
51
+ return f'''<w:r>
52
+ <w:pict>
53
+ <v:shape style="height:{height}px;width:{{{{ {height}*{data_var}['width']/{data_var}['height'] }}}}px">
54
+ <v:imagedata r:id="{{{{%s['rid']}}}}"/>
55
+ </v:shape>
56
+ </w:pict>
57
+ </w:r>''' % data_var
58
+
59
+ content = cls._process_tag(content, r'<xl-signature\s+data="([^"]+)"\s+height="([^"]+)"\s*></xl-signature>', process_signature)
60
+ content = cls._process_tag(content,r'<xl-span(?:[^>]*style="([^"]+)")?[^>]*>(.*?)</xl-span>', process_span)
61
+ data = f'<w:p>{p_props_str}{content}</w:p>'
62
+ return data
63
+
64
+ data = cls._process_tag(xml, r'<xl-p(?:[^>]*style="([^"]+)")?[^>]*>(.*?)</xl-p>', process_paragraph)
65
+ return data
66
+
67
+
68
+ @classmethod
69
+ def decompile(cls, xml: str) -> str:
70
+ """将Ww:p标签转换为xl-p标签"""
71
+ def process_word_paragraph(match):
72
+
73
+ full_p = match.group(0)
74
+ content = match.group(1)
75
+ if not '<w:r' in full_p:
76
+ return full_p
77
+
78
+
79
+ # 提取样式属性
80
+ styles = {}
81
+
82
+ # 提取对齐方式
83
+ align_match = re.search(r'<w:jc\s+w:val="([^"]+)"/>', content)
84
+ if align_match:
85
+ styles['align'] = align_match.group(1)
86
+
87
+ # 提取间距
88
+ spacing_match = re.search(r'<w:spacing\s+([^/>]+)/>', content)
89
+ if spacing_match:
90
+ spacing_attrs = spacing_match.group(1)
91
+ before_match = re.search(r'w:before="([^"]+)"', spacing_attrs)
92
+ after_match = re.search(r'w:after="([^"]+)"', spacing_attrs)
93
+ if before_match:
94
+ styles['padding-top'] = before_match.group(1)
95
+ if after_match:
96
+ styles['padding-bottom'] = after_match.group(1)
97
+
98
+ # 提取字体信息
99
+ font_match = re.search(r'<w:rFonts\s+w:ascii="([^"]+)"[^/]+w:cs="([^"]+)"', content)
100
+ if font_match:
101
+ styles['english'] = font_match.group(1)
102
+ styles['chinese'] = font_match.group(2)
103
+
104
+ # 提取字体大小
105
+ size_match = re.search(r'<w:sz\s+w:val="([^"]+)"/>', content)
106
+ if size_match:
107
+ styles['font-size'] = size_match.group(1)
108
+
109
+ # 检查是否加粗
110
+ if '<w:b/>' in content:
111
+ styles['font-weight'] = 'bold'
112
+
113
+ def process_r(match):
114
+ content = match.group(1).strip()
115
+ r_styles = {}
116
+ underline_match = re.search(r'<w:u w:val="([^>]*)"/>', content)
117
+ if underline_match:
118
+ underline = underline_match.group(1)
119
+ r_styles['underline'] = underline
120
+ r_style_str = cls._build_style_str(r_styles)
121
+
122
+ def process_t(match):
123
+ content = match.group(1).strip()
124
+ return f'{content}'
125
+
126
+ matches = list(re.finditer(r'<w:t(?:\s+[^>]*)?>(.*?)</w:t>', content, re.DOTALL))
127
+ content = ''
128
+ for match in matches:
129
+ full_r = match.group(0)
130
+ full_r = cls._process_tag(full_r, r'<w:t(?:\s+[^>]*)?>(.*?)</w:t>', process_t)
131
+ content += full_r
132
+ return f'<xl-span style="{r_style_str}">{content}</xl-span>'
133
+
134
+ matches = list(re.finditer(r'<w:r(?:\s+[^>]*)?>(.*?)</w:r>', content, re.DOTALL))
135
+ content = ''
136
+ for match in matches:
137
+ full_t = match.group(0)
138
+ full_t = cls._process_tag(full_t, r'<w:r(?:\s+[^>]*)?>(.*?)</w:r>', process_r)
139
+ content += full_t
140
+
141
+ # 构建样式字符串
142
+ style_str = cls._build_style_str(styles)
143
+ style_attr = f' style="{style_str}"' if style_str else ""
144
+
145
+ return f'<xl-p{style_attr}>{content}</xl-p>'
146
+ xml = re.sub(r'<w:p(?![a-zA-Z])([^>]*?)/>',r'<w:p\1></w:p>', xml)
147
+ return cls._process_tag(xml, r'<w:p[^>]*?>(.*?)</w:p>', process_word_paragraph)