hos-m2f 0.5.3__py3-none-any.whl → 0.5.5__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.
- hos_m2f/cli/__init__.py +1 -1
- hos_m2f/converters/md_to_docx.py +172 -41
- hos_m2f/converters/md_to_epub.py +37 -77
- hos_m2f/converters/md_to_html.py +2 -14
- hos_m2f/converters/md_to_json.py +40 -20
- hos_m2f/converters/md_to_latex.py +63 -0
- hos_m2f/converters/md_to_xml.py +40 -20
- hos_m2f/converters/pdf_to_md.py +120 -0
- {hos_m2f-0.5.3.dist-info → hos_m2f-0.5.5.dist-info}/METADATA +1 -1
- hos_m2f-0.5.5.dist-info/RECORD +26 -0
- {hos_m2f-0.5.3.dist-info → hos_m2f-0.5.5.dist-info}/entry_points.txt +1 -0
- {hos_m2f-0.5.3.dist-info → hos_m2f-0.5.5.dist-info}/top_level.txt +1 -0
- tests/__init__.py +1 -0
- tests/test_converters.py +179 -0
- tests/test_latex.py +182 -0
- tests/test_modes.py +202 -0
- hos_m2f-0.5.3.dist-info/RECORD +0 -20
- {hos_m2f-0.5.3.dist-info → hos_m2f-0.5.5.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"""PDF到Markdown格式转换器"""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Optional, Dict
|
|
4
|
+
from hos_m2f.converters.base_converter import BaseConverter
|
|
5
|
+
|
|
6
|
+
# 延迟导入PyPDF2
|
|
7
|
+
pypdf2_available = False
|
|
8
|
+
PdfReader = None
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _check_pypdf2():
|
|
12
|
+
"""检查PyPDF2是否可用"""
|
|
13
|
+
global pypdf2_available, PdfReader
|
|
14
|
+
if not pypdf2_available:
|
|
15
|
+
try:
|
|
16
|
+
from PyPDF2 import PdfReader
|
|
17
|
+
pypdf2_available = True
|
|
18
|
+
except ImportError as e:
|
|
19
|
+
print(f"Warning: PyPDF2 not available: {e}")
|
|
20
|
+
print("PDF to Markdown conversion is disabled.")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class PDFToMDConverter(BaseConverter):
|
|
24
|
+
"""PDF到Markdown格式转换器"""
|
|
25
|
+
|
|
26
|
+
def convert(self, input_content: bytes, options: Optional[Dict[str, Any]] = None) -> bytes:
|
|
27
|
+
"""将PDF转换为Markdown
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
input_content: PDF文件的二进制数据
|
|
31
|
+
options: 转换选项
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
bytes: Markdown文件的二进制数据
|
|
35
|
+
"""
|
|
36
|
+
# 检查PyPDF2是否可用
|
|
37
|
+
_check_pypdf2()
|
|
38
|
+
if not pypdf2_available:
|
|
39
|
+
raise ImportError("PyPDF2 is not available. PDF to Markdown conversion is disabled.")
|
|
40
|
+
|
|
41
|
+
if options is None:
|
|
42
|
+
options = {}
|
|
43
|
+
|
|
44
|
+
# 解析PDF内容
|
|
45
|
+
markdown_content = self._parse_pdf(input_content, options)
|
|
46
|
+
|
|
47
|
+
return markdown_content.encode('utf-8')
|
|
48
|
+
|
|
49
|
+
def _parse_pdf(self, pdf_content: bytes, options: Dict[str, Any]) -> str:
|
|
50
|
+
"""解析PDF内容并转换为Markdown"""
|
|
51
|
+
import io
|
|
52
|
+
|
|
53
|
+
# 创建PDF阅读器
|
|
54
|
+
pdf_reader = PdfReader(io.BytesIO(pdf_content))
|
|
55
|
+
|
|
56
|
+
# 提取文本
|
|
57
|
+
text_content = []
|
|
58
|
+
for page_num in range(len(pdf_reader.pages)):
|
|
59
|
+
page = pdf_reader.pages[page_num]
|
|
60
|
+
text = page.extract_text()
|
|
61
|
+
if text:
|
|
62
|
+
text_content.append(text)
|
|
63
|
+
|
|
64
|
+
# 合并文本
|
|
65
|
+
full_text = '\n\n'.join(text_content)
|
|
66
|
+
|
|
67
|
+
# 转换为Markdown
|
|
68
|
+
markdown_content = self._text_to_markdown(full_text, options)
|
|
69
|
+
|
|
70
|
+
return markdown_content
|
|
71
|
+
|
|
72
|
+
def _text_to_markdown(self, text: str, options: Dict[str, Any]) -> str:
|
|
73
|
+
"""将纯文本转换为Markdown"""
|
|
74
|
+
import re
|
|
75
|
+
|
|
76
|
+
# 分割行
|
|
77
|
+
lines = text.split('\n')
|
|
78
|
+
|
|
79
|
+
# 处理标题
|
|
80
|
+
markdown_lines = []
|
|
81
|
+
for line in lines:
|
|
82
|
+
line = line.strip()
|
|
83
|
+
if not line:
|
|
84
|
+
markdown_lines.append('')
|
|
85
|
+
continue
|
|
86
|
+
|
|
87
|
+
# 简单的标题识别
|
|
88
|
+
# 假设以数字开头的行可能是标题
|
|
89
|
+
if re.match(r'^\d+\.', line):
|
|
90
|
+
# 检查数字级别
|
|
91
|
+
match = re.match(r'^(\d+)\.', line)
|
|
92
|
+
if match:
|
|
93
|
+
level = len(match.group(1).split('.'))
|
|
94
|
+
if level <= 6:
|
|
95
|
+
markdown_lines.append(f'{'#' * level} {line}')
|
|
96
|
+
continue
|
|
97
|
+
|
|
98
|
+
# 检查是否是大写标题
|
|
99
|
+
if line.isupper() and len(line) < 50:
|
|
100
|
+
markdown_lines.append(f'## {line}')
|
|
101
|
+
continue
|
|
102
|
+
|
|
103
|
+
# 普通行
|
|
104
|
+
markdown_lines.append(line)
|
|
105
|
+
|
|
106
|
+
# 合并行
|
|
107
|
+
markdown_content = '\n'.join(markdown_lines)
|
|
108
|
+
|
|
109
|
+
# 处理列表
|
|
110
|
+
markdown_content = re.sub(r'^\s*\-\s(.*)$', r'* \1', markdown_content, flags=re.MULTILINE)
|
|
111
|
+
markdown_content = re.sub(r'^\s*\*\s(.*)$', r'* \1', markdown_content, flags=re.MULTILINE)
|
|
112
|
+
|
|
113
|
+
# 处理粗体
|
|
114
|
+
markdown_content = re.sub(r'\b([A-Z]{3,})\b', r'**\1**', markdown_content)
|
|
115
|
+
|
|
116
|
+
return markdown_content
|
|
117
|
+
|
|
118
|
+
def get_supported_formats(self) -> tuple:
|
|
119
|
+
"""获取支持的格式"""
|
|
120
|
+
return ('pdf', 'md')
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
hos_m2f/__init__.py,sha256=v4k4TbKzPb3nbzgKJHaID3QTSpmTvAsGVHZ-poa870I,178
|
|
2
|
+
hos_m2f/cli/__init__.py,sha256=9Ljh6fmOOFYD39fnew21icQYnIW7sElBo2qWCUNV9KM,57
|
|
3
|
+
hos_m2f/cli/cli.py,sha256=0bWtYmOoNE8h_rrBlwS-4yJwIRnRTtuBx3DWnMkZ4Qo,11920
|
|
4
|
+
hos_m2f/converters/__init__.py,sha256=d88A1sTrQsoMzrTipg7jKTWfI83GJzlRFVFNibajeag,971
|
|
5
|
+
hos_m2f/converters/base_converter.py,sha256=4xqcAFMT82va6VesgM_HybUPIpP77x0DrQSYzb1jf28,696
|
|
6
|
+
hos_m2f/converters/docx_to_md.py,sha256=_HBp3TOD9ZkTFhHR_f3ObLlpDcv0tnSPjPfeGxuvhjM,3064
|
|
7
|
+
hos_m2f/converters/epub_to_md.py,sha256=cFfHmK4IrJKwzEWVE3ue7Jw8tBfWu1q7wG9o7oMf4Pw,4612
|
|
8
|
+
hos_m2f/converters/html_to_md.py,sha256=26GqdynSxKKO2NTxPKgfFs9bTuisLaEIJdBhz4CJ5Eg,4487
|
|
9
|
+
hos_m2f/converters/json_to_md.py,sha256=jeLBQ3jTkgA5a2Kr2gsOPjZB-D4PZxumciFHbyPKNmc,3670
|
|
10
|
+
hos_m2f/converters/md_to_docx.py,sha256=GFAAQppSiCff7pkDAPEmvuoj_f4DMzNWHsbv-9cbqmU,12248
|
|
11
|
+
hos_m2f/converters/md_to_epub.py,sha256=wNoniOSgIz7qiuIagJzqsF6f4pu_HLUigq-w0a_HoFg,2572
|
|
12
|
+
hos_m2f/converters/md_to_html.py,sha256=Pn5K6_QiCdasK1M3hdyr4jlTzzu3OpQLJ-wznGiomPo,2502
|
|
13
|
+
hos_m2f/converters/md_to_json.py,sha256=4VzUQFQ8nStmqm7td6MOFKji25hSiydMZhVJcsRHdYU,11246
|
|
14
|
+
hos_m2f/converters/md_to_latex.py,sha256=7Fra7f984XLLWJTSbjPJP3ljSUldvpc2sqF2QyyPUJg,2348
|
|
15
|
+
hos_m2f/converters/md_to_xml.py,sha256=ARuf4rEX4Of-VdGJI45lAejJV8OmtlHQMK8rltzg6B0,14217
|
|
16
|
+
hos_m2f/converters/pdf_to_md.py,sha256=CgKrvv3CWc6H94nNrDO5nLIegttDzokDpoP2E2oSmEs,3851
|
|
17
|
+
hos_m2f/converters/xml_to_md.py,sha256=zOkaEaSZdvyHag05kIHiWF4VyGMMjfmWmBllBpzwJ4E,4051
|
|
18
|
+
tests/__init__.py,sha256=q1Fh8atmZO-c9dA8JDMvlWaIZxlwABwe_HgNgFNDKJc,16
|
|
19
|
+
tests/test_converters.py,sha256=0sAG1fLR0UjJIWzlKWBR2QU7yl8a8LP8NwwSaU1TI5E,5150
|
|
20
|
+
tests/test_latex.py,sha256=-KCCYKRDu6RoI3gOt0HTtExsW2IJ6KoNfIWeocbdFyY,6619
|
|
21
|
+
tests/test_modes.py,sha256=FFZN1cp4sUJUR5fjbZXo2z-Z4Q5akCRBmSIiR7MCdVA,5887
|
|
22
|
+
hos_m2f-0.5.5.dist-info/METADATA,sha256=qzJMupv3Rq2KQqfw2sJb025gy5nSzJOq6kjuyvqCeXc,1764
|
|
23
|
+
hos_m2f-0.5.5.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
24
|
+
hos_m2f-0.5.5.dist-info/entry_points.txt,sha256=1opnVMOGIufdlQMvWG_e-oTUS0Yca5ysnFKhmYvBmTM,76
|
|
25
|
+
hos_m2f-0.5.5.dist-info/top_level.txt,sha256=EOB5321A6FNFviV_29qnjHtmLG-F6peX7v5s9Rw96V0,14
|
|
26
|
+
hos_m2f-0.5.5.dist-info/RECORD,,
|
tests/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""测试包"""
|
tests/test_converters.py
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
"""测试转换器模块"""
|
|
2
|
+
|
|
3
|
+
import unittest
|
|
4
|
+
import os
|
|
5
|
+
import tempfile
|
|
6
|
+
from hos_m2f.converters.md_to_docx import MDToDOCXConverter
|
|
7
|
+
from hos_m2f.converters.md_to_html import MDToHTMLConverter
|
|
8
|
+
from hos_m2f.converters.md_to_json import MDToJSONConverter
|
|
9
|
+
from hos_m2f.converters.md_to_xml import MDToXMLConverter
|
|
10
|
+
from hos_m2f.converters.md_to_epub import MDToEPUBConverter
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TestConverters(unittest.TestCase):
|
|
14
|
+
"""测试转换器"""
|
|
15
|
+
|
|
16
|
+
def setUp(self):
|
|
17
|
+
"""设置测试环境"""
|
|
18
|
+
# 创建测试用的Markdown内容
|
|
19
|
+
self.test_content = """
|
|
20
|
+
# 测试文档
|
|
21
|
+
|
|
22
|
+
这是一个测试文档,用于测试各种格式转换器。
|
|
23
|
+
|
|
24
|
+
## 章节1
|
|
25
|
+
|
|
26
|
+
这是章节1的内容。
|
|
27
|
+
|
|
28
|
+
### 子章节1.1
|
|
29
|
+
|
|
30
|
+
这是子章节1.1的内容。
|
|
31
|
+
|
|
32
|
+
## 章节2
|
|
33
|
+
|
|
34
|
+
这是章节2的内容。
|
|
35
|
+
|
|
36
|
+
### 表格测试
|
|
37
|
+
|
|
38
|
+
| 列1 | 列2 | 列3 |
|
|
39
|
+
| --- | --- | --- |
|
|
40
|
+
| 行1 | 行1 | 行1 |
|
|
41
|
+
| 行2 | 行2 | 行2 |
|
|
42
|
+
|
|
43
|
+
### Mermaid图表测试
|
|
44
|
+
|
|
45
|
+
```mermaid
|
|
46
|
+
graph TD
|
|
47
|
+
A[开始] --> B[处理]
|
|
48
|
+
B --> C[结束]
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### 链接测试
|
|
52
|
+
|
|
53
|
+
[百度](https://www.baidu.com)
|
|
54
|
+
|
|
55
|
+
### 图片测试
|
|
56
|
+
|
|
57
|
+

|
|
58
|
+
|
|
59
|
+
### 格式化测试
|
|
60
|
+
|
|
61
|
+
*斜体文本*
|
|
62
|
+
|
|
63
|
+
**粗体文本**
|
|
64
|
+
|
|
65
|
+
`代码`
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
print("Hello, world!")
|
|
69
|
+
```
|
|
70
|
+
""".strip()
|
|
71
|
+
|
|
72
|
+
def test_md_to_docx(self):
|
|
73
|
+
"""测试Markdown到DOCX转换"""
|
|
74
|
+
converter = MDToDOCXConverter()
|
|
75
|
+
result = converter.convert(self.test_content)
|
|
76
|
+
self.assertIsInstance(result, bytes)
|
|
77
|
+
self.assertGreater(len(result), 0)
|
|
78
|
+
|
|
79
|
+
# 保存为临时文件,以便手动检查
|
|
80
|
+
with tempfile.NamedTemporaryFile(suffix=".docx", delete=False) as tmp:
|
|
81
|
+
tmp.write(result)
|
|
82
|
+
tmp_path = tmp.name
|
|
83
|
+
|
|
84
|
+
try:
|
|
85
|
+
# 验证文件存在且大小大于0
|
|
86
|
+
self.assertTrue(os.path.exists(tmp_path))
|
|
87
|
+
self.assertGreater(os.path.getsize(tmp_path), 0)
|
|
88
|
+
finally:
|
|
89
|
+
# 清理临时文件
|
|
90
|
+
if os.path.exists(tmp_path):
|
|
91
|
+
os.unlink(tmp_path)
|
|
92
|
+
|
|
93
|
+
def test_md_to_html(self):
|
|
94
|
+
"""测试Markdown到HTML转换"""
|
|
95
|
+
converter = MDToHTMLConverter()
|
|
96
|
+
result = converter.convert(self.test_content)
|
|
97
|
+
self.assertIsInstance(result, bytes)
|
|
98
|
+
self.assertGreater(len(result), 0)
|
|
99
|
+
|
|
100
|
+
# 保存为临时文件,以便手动检查
|
|
101
|
+
with tempfile.NamedTemporaryFile(suffix=".html", delete=False) as tmp:
|
|
102
|
+
tmp.write(result)
|
|
103
|
+
tmp_path = tmp.name
|
|
104
|
+
|
|
105
|
+
try:
|
|
106
|
+
# 验证文件存在且大小大于0
|
|
107
|
+
self.assertTrue(os.path.exists(tmp_path))
|
|
108
|
+
self.assertGreater(os.path.getsize(tmp_path), 0)
|
|
109
|
+
finally:
|
|
110
|
+
# 清理临时文件
|
|
111
|
+
if os.path.exists(tmp_path):
|
|
112
|
+
os.unlink(tmp_path)
|
|
113
|
+
|
|
114
|
+
def test_md_to_json(self):
|
|
115
|
+
"""测试Markdown到JSON转换"""
|
|
116
|
+
converter = MDToJSONConverter()
|
|
117
|
+
result = converter.convert(self.test_content)
|
|
118
|
+
self.assertIsInstance(result, bytes)
|
|
119
|
+
self.assertGreater(len(result), 0)
|
|
120
|
+
|
|
121
|
+
# 保存为临时文件,以便手动检查
|
|
122
|
+
with tempfile.NamedTemporaryFile(suffix=".json", delete=False) as tmp:
|
|
123
|
+
tmp.write(result)
|
|
124
|
+
tmp_path = tmp.name
|
|
125
|
+
|
|
126
|
+
try:
|
|
127
|
+
# 验证文件存在且大小大于0
|
|
128
|
+
self.assertTrue(os.path.exists(tmp_path))
|
|
129
|
+
self.assertGreater(os.path.getsize(tmp_path), 0)
|
|
130
|
+
finally:
|
|
131
|
+
# 清理临时文件
|
|
132
|
+
if os.path.exists(tmp_path):
|
|
133
|
+
os.unlink(tmp_path)
|
|
134
|
+
|
|
135
|
+
def test_md_to_xml(self):
|
|
136
|
+
"""测试Markdown到XML转换"""
|
|
137
|
+
converter = MDToXMLConverter()
|
|
138
|
+
result = converter.convert(self.test_content)
|
|
139
|
+
self.assertIsInstance(result, bytes)
|
|
140
|
+
self.assertGreater(len(result), 0)
|
|
141
|
+
|
|
142
|
+
# 保存为临时文件,以便手动检查
|
|
143
|
+
with tempfile.NamedTemporaryFile(suffix=".xml", delete=False) as tmp:
|
|
144
|
+
tmp.write(result)
|
|
145
|
+
tmp_path = tmp.name
|
|
146
|
+
|
|
147
|
+
try:
|
|
148
|
+
# 验证文件存在且大小大于0
|
|
149
|
+
self.assertTrue(os.path.exists(tmp_path))
|
|
150
|
+
self.assertGreater(os.path.getsize(tmp_path), 0)
|
|
151
|
+
finally:
|
|
152
|
+
# 清理临时文件
|
|
153
|
+
if os.path.exists(tmp_path):
|
|
154
|
+
os.unlink(tmp_path)
|
|
155
|
+
|
|
156
|
+
def test_md_to_epub(self):
|
|
157
|
+
"""测试Markdown到EPUB转换"""
|
|
158
|
+
converter = MDToEPUBConverter()
|
|
159
|
+
result = converter.convert(self.test_content)
|
|
160
|
+
self.assertIsInstance(result, bytes)
|
|
161
|
+
self.assertGreater(len(result), 0)
|
|
162
|
+
|
|
163
|
+
# 保存为临时文件,以便手动检查
|
|
164
|
+
with tempfile.NamedTemporaryFile(suffix=".epub", delete=False) as tmp:
|
|
165
|
+
tmp.write(result)
|
|
166
|
+
tmp_path = tmp.name
|
|
167
|
+
|
|
168
|
+
try:
|
|
169
|
+
# 验证文件存在且大小大于0
|
|
170
|
+
self.assertTrue(os.path.exists(tmp_path))
|
|
171
|
+
self.assertGreater(os.path.getsize(tmp_path), 0)
|
|
172
|
+
finally:
|
|
173
|
+
# 清理临时文件
|
|
174
|
+
if os.path.exists(tmp_path):
|
|
175
|
+
os.unlink(tmp_path)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
if __name__ == '__main__':
|
|
179
|
+
unittest.main()
|
tests/test_latex.py
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"""测试LaTeX渲染器和转换器"""
|
|
2
|
+
|
|
3
|
+
import unittest
|
|
4
|
+
import os
|
|
5
|
+
import tempfile
|
|
6
|
+
from hos_m2f.renderers.latex_renderer import LaTeXRenderer
|
|
7
|
+
from hos_m2f.converters.md_to_latex import MDToLaTeXConverter
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class TestLaTeX(unittest.TestCase):
|
|
11
|
+
"""测试LaTeX渲染器和转换器"""
|
|
12
|
+
|
|
13
|
+
def setUp(self):
|
|
14
|
+
"""设置测试环境"""
|
|
15
|
+
# 创建测试用的Markdown内容
|
|
16
|
+
self.test_content = """
|
|
17
|
+
# 测试文档
|
|
18
|
+
|
|
19
|
+
## 摘要
|
|
20
|
+
|
|
21
|
+
这是一个测试文档,用于测试LaTeX渲染功能。
|
|
22
|
+
|
|
23
|
+
## 引言
|
|
24
|
+
|
|
25
|
+
这是引言章节的内容。
|
|
26
|
+
|
|
27
|
+
### 背景
|
|
28
|
+
|
|
29
|
+
这是背景部分的内容。
|
|
30
|
+
|
|
31
|
+
## 方法
|
|
32
|
+
|
|
33
|
+
这是方法章节的内容。
|
|
34
|
+
|
|
35
|
+
### 实验设计
|
|
36
|
+
|
|
37
|
+
这是实验设计部分的内容。
|
|
38
|
+
|
|
39
|
+
## 结果
|
|
40
|
+
|
|
41
|
+
这是结果章节的内容。
|
|
42
|
+
|
|
43
|
+
### 数据表格
|
|
44
|
+
|
|
45
|
+
| 列1 | 列2 | 列3 |
|
|
46
|
+
| --- | --- | --- |
|
|
47
|
+
| 行1 | 行1 | 行1 |
|
|
48
|
+
| 行2 | 行2 | 行2 |
|
|
49
|
+
|
|
50
|
+
### 代码示例
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
print("Hello, world!")
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## 讨论
|
|
57
|
+
|
|
58
|
+
这是讨论章节的内容。
|
|
59
|
+
|
|
60
|
+
## 结论
|
|
61
|
+
|
|
62
|
+
这是结论章节的内容。
|
|
63
|
+
|
|
64
|
+
## 参考文献
|
|
65
|
+
|
|
66
|
+
[1] 参考文献1
|
|
67
|
+
[2] 参考文献2
|
|
68
|
+
""".strip()
|
|
69
|
+
|
|
70
|
+
# 创建测试用的结构化内容
|
|
71
|
+
self.structured_content = {
|
|
72
|
+
"metadata": {
|
|
73
|
+
"title": "测试文档",
|
|
74
|
+
"author": "测试作者",
|
|
75
|
+
"date": "2023-01-01",
|
|
76
|
+
"abstract": "这是一个测试文档,用于测试LaTeX渲染功能。",
|
|
77
|
+
"keywords": ["测试", "LaTeX", "渲染"]
|
|
78
|
+
},
|
|
79
|
+
"structure": [
|
|
80
|
+
{"level": 1, "title": "测试文档", "line_number": 1},
|
|
81
|
+
{"level": 2, "title": "摘要", "line_number": 3},
|
|
82
|
+
{"level": 2, "title": "引言", "line_number": 7},
|
|
83
|
+
{"level": 3, "title": "背景", "line_number": 9},
|
|
84
|
+
{"level": 2, "title": "方法", "line_number": 13},
|
|
85
|
+
{"level": 3, "title": "实验设计", "line_number": 15},
|
|
86
|
+
{"level": 2, "title": "结果", "line_number": 19},
|
|
87
|
+
{"level": 3, "title": "数据表格", "line_number": 21},
|
|
88
|
+
{"level": 3, "title": "代码示例", "line_number": 29},
|
|
89
|
+
{"level": 2, "title": "讨论", "line_number": 35},
|
|
90
|
+
{"level": 2, "title": "结论", "line_number": 39},
|
|
91
|
+
{"level": 2, "title": "参考文献", "line_number": 43}
|
|
92
|
+
],
|
|
93
|
+
"chapters": [
|
|
94
|
+
{"title": "测试文档", "content": "", "level": 1, "start_line": 1, "end_line": 1},
|
|
95
|
+
{"title": "摘要", "content": "这是一个测试文档,用于测试LaTeX渲染功能。", "level": 2, "start_line": 3, "end_line": 5},
|
|
96
|
+
{"title": "引言", "content": "这是引言章节的内容。", "level": 2, "start_line": 7, "end_line": 8},
|
|
97
|
+
{"title": "背景", "content": "这是背景部分的内容。", "level": 3, "start_line": 9, "end_line": 11},
|
|
98
|
+
{"title": "方法", "content": "这是方法章节的内容。", "level": 2, "start_line": 13, "end_line": 14},
|
|
99
|
+
{"title": "实验设计", "content": "这是实验设计部分的内容。", "level": 3, "start_line": 15, "end_line": 17},
|
|
100
|
+
{"title": "结果", "content": "这是结果章节的内容。", "level": 2, "start_line": 19, "end_line": 20},
|
|
101
|
+
{"title": "数据表格", "content": "| 列1 | 列2 | 列3 |\n| --- | --- | --- |\n| 行1 | 行1 | 行1 |\n| 行2 | 行2 | 行2 |", "level": 3, "start_line": 21, "end_line": 28},
|
|
102
|
+
{"title": "代码示例", "content": "```python\nprint(\"Hello, world!\")\n```", "level": 3, "start_line": 29, "end_line": 34},
|
|
103
|
+
{"title": "讨论", "content": "这是讨论章节的内容。", "level": 2, "start_line": 35, "end_line": 37},
|
|
104
|
+
{"title": "结论", "content": "这是结论章节的内容。", "level": 2, "start_line": 39, "end_line": 41},
|
|
105
|
+
{"title": "参考文献", "content": "[1] 参考文献1\n[2] 参考文献2", "level": 2, "start_line": 43, "end_line": 46}
|
|
106
|
+
],
|
|
107
|
+
"references": [
|
|
108
|
+
{"text": "参考文献1"},
|
|
109
|
+
{"text": "参考文献2"}
|
|
110
|
+
]
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
def test_latex_renderer(self):
|
|
114
|
+
"""测试LaTeX渲染器"""
|
|
115
|
+
renderer = LaTeXRenderer()
|
|
116
|
+
|
|
117
|
+
# 测试渲染功能
|
|
118
|
+
latex_content = renderer.render(self.structured_content)
|
|
119
|
+
self.assertIsInstance(latex_content, bytes)
|
|
120
|
+
self.assertGreater(len(latex_content), 0)
|
|
121
|
+
|
|
122
|
+
# 保存为临时文件,以便手动检查
|
|
123
|
+
with tempfile.NamedTemporaryFile(suffix=".tex", delete=False) as tmp:
|
|
124
|
+
tmp.write(latex_content)
|
|
125
|
+
tmp_path = tmp.name
|
|
126
|
+
|
|
127
|
+
try:
|
|
128
|
+
# 验证文件存在且大小大于0
|
|
129
|
+
self.assertTrue(os.path.exists(tmp_path))
|
|
130
|
+
self.assertGreater(os.path.getsize(tmp_path), 0)
|
|
131
|
+
finally:
|
|
132
|
+
# 清理临时文件
|
|
133
|
+
if os.path.exists(tmp_path):
|
|
134
|
+
os.unlink(tmp_path)
|
|
135
|
+
|
|
136
|
+
def test_md_to_latex_converter(self):
|
|
137
|
+
"""测试Markdown到LaTeX转换器"""
|
|
138
|
+
converter = MDToLaTeXConverter()
|
|
139
|
+
|
|
140
|
+
# 测试转换功能
|
|
141
|
+
result = converter.convert(self.test_content)
|
|
142
|
+
self.assertIsInstance(result, bytes)
|
|
143
|
+
self.assertGreater(len(result), 0)
|
|
144
|
+
|
|
145
|
+
# 保存为临时文件,以便手动检查
|
|
146
|
+
with tempfile.NamedTemporaryFile(suffix=".tex", delete=False) as tmp:
|
|
147
|
+
tmp.write(result)
|
|
148
|
+
tmp_path = tmp.name
|
|
149
|
+
|
|
150
|
+
try:
|
|
151
|
+
# 验证文件存在且大小大于0
|
|
152
|
+
self.assertTrue(os.path.exists(tmp_path))
|
|
153
|
+
self.assertGreater(os.path.getsize(tmp_path), 0)
|
|
154
|
+
finally:
|
|
155
|
+
# 清理临时文件
|
|
156
|
+
if os.path.exists(tmp_path):
|
|
157
|
+
os.unlink(tmp_path)
|
|
158
|
+
|
|
159
|
+
def test_latex_with_options(self):
|
|
160
|
+
"""测试带选项的LaTeX渲染"""
|
|
161
|
+
renderer = LaTeXRenderer()
|
|
162
|
+
converter = MDToLaTeXConverter()
|
|
163
|
+
|
|
164
|
+
# 测试带选项的渲染
|
|
165
|
+
options = {
|
|
166
|
+
"document_class": "article",
|
|
167
|
+
"document_options": "a4paper, 12pt",
|
|
168
|
+
"table_of_contents": True
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
latex_content = renderer.render(self.structured_content, options)
|
|
172
|
+
self.assertIsInstance(latex_content, bytes)
|
|
173
|
+
self.assertGreater(len(latex_content), 0)
|
|
174
|
+
|
|
175
|
+
# 测试带选项的转换
|
|
176
|
+
result = converter.convert(self.test_content, options)
|
|
177
|
+
self.assertIsInstance(result, bytes)
|
|
178
|
+
self.assertGreater(len(result), 0)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
if __name__ == '__main__':
|
|
182
|
+
unittest.main()
|