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.
- xl_docx-0.6.7/.gitignore +3 -0
- xl_docx-0.6.7/PKG-INFO +14 -0
- xl_docx-0.6.7/README.md +0 -0
- xl_docx-0.6.7/assets/h.docx +0 -0
- xl_docx-0.6.7/assets/img/word-template.png +0 -0
- xl_docx-0.6.7/assets/img/wordx-tool.png +0 -0
- xl_docx-0.6.7/assets/v.docx +0 -0
- xl_docx-0.6.7/pyproject.toml +27 -0
- xl_docx-0.6.7/src/xl_word/__init__.py +4 -0
- xl_docx-0.6.7/src/xl_word/__main__.py +5 -0
- xl_docx-0.6.7/src/xl_word/compiler/__init__.py +79 -0
- xl_docx-0.6.7/src/xl_word/compiler/processors/__init__.py +6 -0
- xl_docx-0.6.7/src/xl_word/compiler/processors/base.py +73 -0
- xl_docx-0.6.7/src/xl_word/compiler/processors/directive.py +75 -0
- xl_docx-0.6.7/src/xl_word/compiler/processors/pager.py +196 -0
- xl_docx-0.6.7/src/xl_word/compiler/processors/paragraph.py +147 -0
- xl_docx-0.6.7/src/xl_word/compiler/processors/style.py +70 -0
- xl_docx-0.6.7/src/xl_word/compiler/processors/table.py +246 -0
- xl_docx-0.6.7/src/xl_word/document.py +87 -0
- xl_docx-0.6.7/src/xl_word/h.docx +0 -0
- xl_docx-0.6.7/src/xl_word/mixins.py +102 -0
- xl_docx-0.6.7/src/xl_word/sheet.py +152 -0
- xl_docx-0.6.7/src/xl_word/tool/__init__.py +333 -0
- xl_docx-0.6.7/src/xl_word/tool/build.bat +27 -0
- xl_docx-0.6.7/src/xl_word/tool/document.xml +0 -0
- xl_docx-0.6.7/src/xl_word/tool/gui.py +120 -0
- xl_docx-0.6.7/src/xl_word/tool/h.docx +0 -0
- xl_docx-0.6.7/src/xl_word/tool/tool.spec +44 -0
- xl_docx-0.6.7/src/xl_word/tool/utility.py +150 -0
- xl_docx-0.6.7/src/xl_word/tool/v.docx +0 -0
- xl_docx-0.6.7/src/xl_word/utils/fake_zip.py +138 -0
- xl_docx-0.6.7/src/xl_word/utils/tree.py +56 -0
- xl_docx-0.6.7/src/xl_word/v.docx +0 -0
- xl_docx-0.6.7/src/xl_word/word_file.py +57 -0
xl_docx-0.6.7/.gitignore
ADDED
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
|
xl_docx-0.6.7/README.md
ADDED
|
File without changes
|
|
Binary file
|
|
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,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)
|