hos-m2f 0.5.3__tar.gz → 0.5.4__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 (29) hide show
  1. {hos_m2f-0.5.3 → hos_m2f-0.5.4}/PKG-INFO +1 -1
  2. {hos_m2f-0.5.3 → hos_m2f-0.5.4}/hos_m2f/cli/__init__.py +1 -1
  3. hos_m2f-0.5.4/hos_m2f/converters/md_to_docx.py +257 -0
  4. {hos_m2f-0.5.3 → hos_m2f-0.5.4}/hos_m2f/converters/md_to_epub.py +2 -14
  5. {hos_m2f-0.5.3 → hos_m2f-0.5.4}/hos_m2f/converters/md_to_html.py +2 -14
  6. {hos_m2f-0.5.3 → hos_m2f-0.5.4}/hos_m2f/converters/md_to_json.py +40 -20
  7. {hos_m2f-0.5.3 → hos_m2f-0.5.4}/hos_m2f/converters/md_to_xml.py +40 -20
  8. {hos_m2f-0.5.3 → hos_m2f-0.5.4}/hos_m2f.egg-info/PKG-INFO +1 -1
  9. {hos_m2f-0.5.3 → hos_m2f-0.5.4}/hos_m2f.egg-info/SOURCES.txt +4 -1
  10. {hos_m2f-0.5.3 → hos_m2f-0.5.4}/hos_m2f.egg-info/entry_points.txt +1 -0
  11. {hos_m2f-0.5.3 → hos_m2f-0.5.4}/hos_m2f.egg-info/top_level.txt +1 -0
  12. {hos_m2f-0.5.3 → hos_m2f-0.5.4}/setup.py +2 -1
  13. hos_m2f-0.5.4/tests/__init__.py +1 -0
  14. hos_m2f-0.5.4/tests/test_converters.py +179 -0
  15. hos_m2f-0.5.4/tests/test_modes.py +202 -0
  16. hos_m2f-0.5.3/hos_m2f/converters/md_to_docx.py +0 -171
  17. {hos_m2f-0.5.3 → hos_m2f-0.5.4}/README.md +0 -0
  18. {hos_m2f-0.5.3 → hos_m2f-0.5.4}/hos_m2f/__init__.py +0 -0
  19. {hos_m2f-0.5.3 → hos_m2f-0.5.4}/hos_m2f/cli/cli.py +0 -0
  20. {hos_m2f-0.5.3 → hos_m2f-0.5.4}/hos_m2f/converters/__init__.py +0 -0
  21. {hos_m2f-0.5.3 → hos_m2f-0.5.4}/hos_m2f/converters/base_converter.py +0 -0
  22. {hos_m2f-0.5.3 → hos_m2f-0.5.4}/hos_m2f/converters/docx_to_md.py +0 -0
  23. {hos_m2f-0.5.3 → hos_m2f-0.5.4}/hos_m2f/converters/epub_to_md.py +0 -0
  24. {hos_m2f-0.5.3 → hos_m2f-0.5.4}/hos_m2f/converters/html_to_md.py +0 -0
  25. {hos_m2f-0.5.3 → hos_m2f-0.5.4}/hos_m2f/converters/json_to_md.py +0 -0
  26. {hos_m2f-0.5.3 → hos_m2f-0.5.4}/hos_m2f/converters/xml_to_md.py +0 -0
  27. {hos_m2f-0.5.3 → hos_m2f-0.5.4}/hos_m2f.egg-info/dependency_links.txt +0 -0
  28. {hos_m2f-0.5.3 → hos_m2f-0.5.4}/hos_m2f.egg-info/requires.txt +0 -0
  29. {hos_m2f-0.5.3 → hos_m2f-0.5.4}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hos-m2f
3
- Version: 0.5.3
3
+ Version: 0.5.4
4
4
  Summary: HOS-M2F: Markdown to Industry Standard Format Compiler Engine
5
5
  Author: HOS Team
6
6
  Author-email: team@hos-m2f.com
@@ -1,5 +1,5 @@
1
1
  """CLI模块"""
2
2
 
3
- from hos_m2f.cli.cli import CLI
3
+ from .cli import CLI
4
4
 
5
5
  __all__ = ['CLI']
@@ -0,0 +1,257 @@
1
+ """Markdown到DOCX格式转换器"""
2
+
3
+ from typing import Any, Optional, Dict
4
+ from docx import Document
5
+ from docx.shared import Inches, Pt, RGBColor
6
+ from docx.enum.text import WD_ALIGN_PARAGRAPH, WD_UNDERLINE
7
+ from docx.enum.style import WD_STYLE_TYPE
8
+ from hos_m2f.converters.base_converter import BaseConverter
9
+ import mistune
10
+
11
+
12
+ class MDToDOCXConverter(BaseConverter):
13
+ """Markdown到DOCX格式转换器"""
14
+
15
+ def convert(self, input_content: str, options: Optional[Dict[str, Any]] = None) -> bytes:
16
+ """将Markdown转换为DOCX
17
+
18
+ Args:
19
+ input_content: Markdown内容
20
+ options: 转换选项
21
+
22
+ Returns:
23
+ bytes: DOCX文件的二进制数据
24
+ """
25
+ if options is None:
26
+ options = {}
27
+
28
+ # 创建文档
29
+ doc = Document()
30
+
31
+ # 设置默认样式
32
+ self._setup_styles(doc)
33
+
34
+ # 自定义渲染器
35
+ class DOCXRenderer(mistune.HTMLRenderer):
36
+ def __init__(self, doc):
37
+ super().__init__()
38
+ self.doc = doc
39
+ self.current_paragraph = None
40
+ self.list_level = 0
41
+ self.lists = []
42
+
43
+ def paragraph(self, text):
44
+ if text.strip():
45
+ p = self.doc.add_paragraph()
46
+ p.add_run(text)
47
+ return ''
48
+
49
+ def heading(self, text, level):
50
+ if level == 1:
51
+ self.doc.add_heading(text, level=0)
52
+ else:
53
+ self.doc.add_heading(text, level=level-1)
54
+ return ''
55
+
56
+ def list(self, text, ordered, level, start=None):
57
+ self.list_level += 1
58
+ self.lists.append(ordered)
59
+ return ''
60
+
61
+ def list_item(self, text, level):
62
+ if text.strip():
63
+ p = self.doc.add_paragraph(
64
+ text,
65
+ style='List Number' if self.lists[level-1] else 'List Bullet'
66
+ )
67
+ # 缩进
68
+ for i in range(level-1):
69
+ p.paragraph_format.left_indent += Inches(0.5)
70
+ return ''
71
+
72
+ def list_end(self, level):
73
+ self.list_level -= 1
74
+ if self.lists:
75
+ self.lists.pop()
76
+ return ''
77
+
78
+ def table(self, text):
79
+ # 解析Markdown表格并转换为DOCX表格
80
+ try:
81
+ # 分割表格行
82
+ rows = text.strip().split('\n')
83
+ if not rows:
84
+ return ''
85
+
86
+ # 解析表头
87
+ header_cells = [cell.strip() for cell in rows[0].split('|') if cell.strip()]
88
+ if not header_cells:
89
+ return ''
90
+
91
+ # 创建表格
92
+ table = self.doc.add_table(rows=1, cols=len(header_cells))
93
+ table.style = 'Table Grid'
94
+
95
+ # 填充表头
96
+ header_row = table.rows[0]
97
+ for i, cell_text in enumerate(header_cells):
98
+ header_row.cells[i].text = cell_text
99
+
100
+ # 跳过分隔线行
101
+ if len(rows) > 1 and '---' in rows[1]:
102
+ data_rows = rows[2:]
103
+ else:
104
+ data_rows = rows[1:]
105
+
106
+ # 填充数据行
107
+ for row in data_rows:
108
+ cells = [cell.strip() for cell in row.split('|') if cell.strip()]
109
+ if cells:
110
+ new_row = table.add_row()
111
+ for i, cell_text in enumerate(cells):
112
+ if i < len(new_row.cells):
113
+ new_row.cells[i].text = cell_text
114
+ except Exception as e:
115
+ # 如果解析失败,回退到简单处理
116
+ self.doc.add_paragraph('Table: ' + text[:100] + '...')
117
+ return ''
118
+
119
+ def image(self, text, url=None, title=None, alt=None):
120
+ try:
121
+ # 尝试处理本地和远程图片
122
+ import os
123
+ import requests
124
+ from io import BytesIO
125
+
126
+ # 使用alt作为替代文本
127
+ if alt is None:
128
+ alt = text
129
+
130
+ # 检查是否有图片URL
131
+ if not url:
132
+ self.doc.add_paragraph(f'Image: {alt}')
133
+ return ''
134
+
135
+ # 检查是否是本地图片
136
+ if os.path.exists(url):
137
+ # 添加本地图片
138
+ self.doc.add_picture(url)
139
+ else:
140
+ # 尝试从网络获取图片
141
+ response = requests.get(url, timeout=5)
142
+ if response.status_code == 200:
143
+ # 添加远程图片
144
+ image_stream = BytesIO(response.content)
145
+ self.doc.add_picture(image_stream)
146
+ else:
147
+ # 如果获取失败,添加图片描述
148
+ self.doc.add_paragraph(f'Image: {alt} ({url})')
149
+ except Exception as e:
150
+ # 如果处理失败,添加图片描述
151
+ self.doc.add_paragraph(f'Image: {alt or text} ({url or ""})')
152
+ return ''
153
+
154
+ def link(self, text, url=None, title=None):
155
+ if text and url:
156
+ # 简化处理,直接添加文本和链接
157
+ p = self.doc.add_paragraph()
158
+ run = p.add_run(text)
159
+ run.font.color.rgb = RGBColor(0, 0, 255) # 蓝色
160
+ run.underline = WD_UNDERLINE.SINGLE
161
+ p.add_run(f' ({url})')
162
+ elif text:
163
+ p = self.doc.add_paragraph(text)
164
+ elif url:
165
+ p = self.doc.add_paragraph(url)
166
+ return ''
167
+
168
+ def emphasis(self, text):
169
+ # 直接添加斜体文本
170
+ p = self.doc.add_paragraph()
171
+ run = p.add_run(text)
172
+ run.italic = True
173
+ return ''
174
+
175
+ def strong(self, text):
176
+ # 直接添加粗体文本
177
+ p = self.doc.add_paragraph()
178
+ run = p.add_run(text)
179
+ run.bold = True
180
+ return ''
181
+
182
+ def codespan(self, text):
183
+ p = self.doc.add_paragraph()
184
+ run = p.add_run(text)
185
+ run.font.name = 'Courier New'
186
+ return ''
187
+
188
+ def block_code(self, code, info=None):
189
+ # 处理Mermaid图表
190
+ if info == 'mermaid':
191
+ try:
192
+ # 尝试渲染Mermaid图表为图片
193
+ mermaid_image = self._render_mermaid(code)
194
+ if mermaid_image:
195
+ # 添加图片
196
+ self.doc.add_picture(mermaid_image)
197
+ return ''
198
+ else:
199
+ # 如果渲染失败,添加代码块
200
+ p = self.doc.add_paragraph('Mermaid Chart:')
201
+ p = self.doc.add_paragraph(code)
202
+ p.paragraph_format.left_indent = Inches(0.5)
203
+ return ''
204
+ except Exception as e:
205
+ # 如果处理失败,添加代码块
206
+ p = self.doc.add_paragraph('Mermaid Chart:')
207
+ p = self.doc.add_paragraph(code)
208
+ p.paragraph_format.left_indent = Inches(0.5)
209
+ return ''
210
+ else:
211
+ # 处理普通代码块
212
+ p = self.doc.add_paragraph()
213
+ run = p.add_run(code)
214
+ run.font.name = 'Courier New'
215
+ p.paragraph_format.left_indent = Inches(0.5)
216
+ return ''
217
+
218
+ def _render_mermaid(self, mermaid_code):
219
+ """渲染Mermaid图表为图片"""
220
+ # 简化处理,实际项目中需要使用mermaid-cli或其他工具
221
+ # 这里返回None,回退到显示代码块
222
+ return None
223
+
224
+ # 渲染Markdown
225
+ renderer = DOCXRenderer(doc)
226
+ markdown = mistune.create_markdown(renderer=renderer)
227
+ markdown(input_content)
228
+
229
+ # 保存为二进制数据
230
+ import io
231
+ output = io.BytesIO()
232
+ doc.save(output)
233
+ output.seek(0)
234
+
235
+ return output.getvalue()
236
+
237
+ def _setup_styles(self, doc):
238
+ """设置文档样式"""
239
+ styles = doc.styles
240
+
241
+ # 设置正文样式
242
+ normal_style = styles['Normal']
243
+ font = normal_style.font
244
+ font.name = 'Microsoft YaHei'
245
+ font.size = Pt(12)
246
+
247
+ # 设置标题样式
248
+ for i in range(1, 6):
249
+ heading_style = styles[f'Heading {i}']
250
+ font = heading_style.font
251
+ font.name = 'Microsoft YaHei'
252
+ font.size = Pt(14 + (6 - i) * 2)
253
+ font.bold = True
254
+
255
+ def get_supported_formats(self) -> tuple:
256
+ """获取支持的格式"""
257
+ return ('markdown', 'docx')
@@ -44,20 +44,8 @@ class MDToEPUBConverter(BaseConverter):
44
44
  book.set_cover('images/cover.jpg', cover_image)
45
45
 
46
46
  # 解析Markdown
47
- markdown = mistune.create_markdown(
48
- plugins=[
49
- 'url',
50
- 'abbr',
51
- 'def_list',
52
- 'footnotes',
53
- 'tables',
54
- 'task_lists',
55
- 'strikethrough',
56
- 'highlight',
57
- 'superscript',
58
- 'subscript'
59
- ]
60
- )
47
+ markdown = mistune.create_markdown()
48
+
61
49
 
62
50
  # 转换为HTML
63
51
  html_content = markdown(input_content)
@@ -22,20 +22,8 @@ class MDToHTMLConverter(BaseConverter):
22
22
  options = {}
23
23
 
24
24
  # 解析Markdown
25
- markdown = mistune.create_markdown(
26
- plugins=[
27
- 'url',
28
- 'abbr',
29
- 'def_list',
30
- 'footnotes',
31
- 'tables',
32
- 'task_lists',
33
- 'strikethrough',
34
- 'highlight',
35
- 'superscript',
36
- 'subscript'
37
- ]
38
- )
25
+ markdown = mistune.create_markdown()
26
+
39
27
 
40
28
  # 转换为HTML
41
29
  html_content = markdown(input_content)
@@ -193,19 +193,30 @@ class MDToJSONConverter(BaseConverter):
193
193
  language = line[3:].strip()
194
194
 
195
195
  # 读取代码内容
196
- for i, code_line in enumerate(lines[lines.index(line)+1:]):
197
- if code_line.startswith('```'):
198
- break
199
- code_lines.append(code_line)
200
-
201
- structure['children'].append({
202
- 'type': 'code_block',
203
- 'language': language,
204
- 'content': '\n'.join(code_lines)
205
- })
206
-
207
- # 跳过已处理的代码行
208
- lines = lines[:lines.index(line)] + lines[lines.index(line)+i+2:]
196
+ try:
197
+ line_idx = lines.index(line)
198
+ code_end_idx = line_idx + 1
199
+ for i, code_line in enumerate(lines[line_idx+1:]):
200
+ if code_line.startswith('```'):
201
+ code_end_idx = line_idx + i + 1
202
+ break
203
+ code_lines.append(code_line)
204
+ code_end_idx = line_idx + i + 1
205
+
206
+ structure['children'].append({
207
+ 'type': 'code_block',
208
+ 'language': language,
209
+ 'content': '\n'.join(code_lines)
210
+ })
211
+
212
+ # 跳过已处理的代码行
213
+ if code_end_idx < len(lines):
214
+ lines = lines[:line_idx] + lines[code_end_idx+1:]
215
+ else:
216
+ lines = lines[:line_idx]
217
+ except ValueError:
218
+ # 如果找不到行,跳过代码块解析
219
+ continue
209
220
 
210
221
  # 处理表格
211
222
  elif line.startswith('|') and '|' in line[1:]:
@@ -231,19 +242,28 @@ class MDToJSONConverter(BaseConverter):
231
242
  table_lines = [line]
232
243
 
233
244
  # 读取表格内容
234
- for i, table_line in enumerate(lines[lines.index(line)+1:]):
235
- if table_line.startswith('|'):
236
- table_lines.append(table_line)
237
- else:
238
- break
245
+ try:
246
+ line_idx = lines.index(line)
247
+ for i, table_line in enumerate(lines[line_idx+1:]):
248
+ if table_line.startswith('|'):
249
+ table_lines.append(table_line)
250
+ else:
251
+ break
252
+ except ValueError:
253
+ # 如果找不到行,跳过表格解析
254
+ continue
239
255
 
240
256
  # 解析表格结构
241
257
  if len(table_lines) >= 2:
242
258
  headers = [h.strip() for h in table_lines[0].split('|') if h.strip()]
243
259
  rows = []
244
260
 
245
- # 跳过分隔线
246
- for table_line in table_lines[2:]:
261
+ # 跳过分隔线(如果存在)
262
+ start_idx = 1
263
+ if len(table_lines) > 1 and any('---' in cell for cell in table_lines[1].split('|')):
264
+ start_idx = 2
265
+
266
+ for table_line in table_lines[start_idx:]:
247
267
  cells = [c.strip() for c in table_line.split('|') if c.strip()]
248
268
  if cells:
249
269
  rows.append(dict(zip(headers, cells)))
@@ -200,19 +200,30 @@ class MDToXMLConverter(BaseConverter):
200
200
  language = line[3:].strip()
201
201
 
202
202
  # 读取代码内容
203
- for i, code_line in enumerate(lines[lines.index(line)+1:]):
204
- if code_line.startswith('```'):
205
- break
206
- code_lines.append(code_line)
207
-
208
- structure['children'].append({
209
- 'type': 'code_block',
210
- 'language': language,
211
- 'content': '\n'.join(code_lines)
212
- })
213
-
214
- # 跳过已处理的代码行
215
- lines = lines[:lines.index(line)] + lines[lines.index(line)+i+2:]
203
+ try:
204
+ line_idx = lines.index(line)
205
+ code_end_idx = line_idx + 1
206
+ for i, code_line in enumerate(lines[line_idx+1:]):
207
+ if code_line.startswith('```'):
208
+ code_end_idx = line_idx + i + 1
209
+ break
210
+ code_lines.append(code_line)
211
+ code_end_idx = line_idx + i + 1
212
+
213
+ structure['children'].append({
214
+ 'type': 'code_block',
215
+ 'language': language,
216
+ 'content': '\n'.join(code_lines)
217
+ })
218
+
219
+ # 跳过已处理的代码行
220
+ if code_end_idx < len(lines):
221
+ lines = lines[:line_idx] + lines[code_end_idx+1:]
222
+ else:
223
+ lines = lines[:line_idx]
224
+ except ValueError:
225
+ # 如果找不到行,跳过代码块解析
226
+ continue
216
227
 
217
228
  # 处理表格
218
229
  elif line.startswith('|') and '|' in line[1:]:
@@ -238,19 +249,28 @@ class MDToXMLConverter(BaseConverter):
238
249
  table_lines = [line]
239
250
 
240
251
  # 读取表格内容
241
- for i, table_line in enumerate(lines[lines.index(line)+1:]):
242
- if table_line.startswith('|'):
243
- table_lines.append(table_line)
244
- else:
245
- break
252
+ try:
253
+ line_idx = lines.index(line)
254
+ for i, table_line in enumerate(lines[line_idx+1:]):
255
+ if table_line.startswith('|'):
256
+ table_lines.append(table_line)
257
+ else:
258
+ break
259
+ except ValueError:
260
+ # 如果找不到行,跳过表格解析
261
+ continue
246
262
 
247
263
  # 解析表格结构
248
264
  if len(table_lines) >= 2:
249
265
  headers = [h.strip() for h in table_lines[0].split('|') if h.strip()]
250
266
  rows = []
251
267
 
252
- # 跳过分隔线
253
- for table_line in table_lines[2:]:
268
+ # 跳过分隔线(如果存在)
269
+ start_idx = 1
270
+ if len(table_lines) > 1 and any('---' in cell for cell in table_lines[1].split('|')):
271
+ start_idx = 2
272
+
273
+ for table_line in table_lines[start_idx:]:
254
274
  cells = [c.strip() for c in table_line.split('|') if c.strip()]
255
275
  if cells:
256
276
  rows.append(dict(zip(headers, cells)))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hos-m2f
3
- Version: 0.5.3
3
+ Version: 0.5.4
4
4
  Summary: HOS-M2F: Markdown to Industry Standard Format Compiler Engine
5
5
  Author: HOS Team
6
6
  Author-email: team@hos-m2f.com
@@ -20,4 +20,7 @@ hos_m2f/converters/md_to_epub.py
20
20
  hos_m2f/converters/md_to_html.py
21
21
  hos_m2f/converters/md_to_json.py
22
22
  hos_m2f/converters/md_to_xml.py
23
- hos_m2f/converters/xml_to_md.py
23
+ hos_m2f/converters/xml_to_md.py
24
+ tests/__init__.py
25
+ tests/test_converters.py
26
+ tests/test_modes.py
@@ -1,2 +1,3 @@
1
1
  [console_scripts]
2
+ hos = hos_m2f.cli.cli:main
2
3
  hos-m2f = hos_m2f.cli.cli:main
@@ -1 +1,2 @@
1
1
  hos_m2f
2
+ tests
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
2
2
 
3
3
  setup(
4
4
  name="hos-m2f",
5
- version="0.5.3",
5
+ version="0.5.4",
6
6
  description="HOS-M2F: Markdown to Industry Standard Format Compiler Engine",
7
7
  long_description="""HOS-M2F is a powerful compiler engine that converts Markdown files to various industry standard formats.
8
8
 
@@ -40,6 +40,7 @@ HOS-M2F simplifies the process of creating professionally formatted documents fr
40
40
  ],
41
41
  entry_points={
42
42
  "console_scripts": [
43
+ "hos=hos_m2f.cli.cli:main",
43
44
  "hos-m2f=hos_m2f.cli.cli:main"
44
45
  ]
45
46
  },
@@ -0,0 +1 @@
1
+ """测试包"""
@@ -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
+ ![测试图片](https://example.com/test.jpg)
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()
@@ -0,0 +1,202 @@
1
+ """测试模式模块"""
2
+
3
+ import unittest
4
+ import os
5
+ import tempfile
6
+ from hos_m2f.modes.book_mode import BookMode
7
+ from hos_m2f.modes.patent_mode import PatentMode
8
+ from hos_m2f.modes.sop_mode import SOPMode
9
+ from hos_m2f.modes.paper_mode import PaperMode
10
+
11
+
12
+ class TestModes(unittest.TestCase):
13
+ """测试模式"""
14
+
15
+ def setUp(self):
16
+ """设置测试环境"""
17
+ # 创建测试用的Markdown内容
18
+ self.book_content = """
19
+ # 第1章 引言
20
+
21
+ 这是引言章节的内容。
22
+
23
+ ## 1.1 背景
24
+
25
+ 这是背景部分的内容。
26
+
27
+ # 第2章 方法
28
+
29
+ 这是方法章节的内容。
30
+
31
+ ## 2.1 实验设计
32
+
33
+ 这是实验设计部分的内容。
34
+
35
+ # 第3章 结果
36
+
37
+ 这是结果章节的内容。
38
+
39
+ # 第4章 结论
40
+
41
+ 这是结论章节的内容。
42
+ """.strip()
43
+
44
+ self.patent_content = """
45
+ # 一种新型的太阳能电池
46
+
47
+ ## 摘要
48
+
49
+ 本发明涉及一种新型的太阳能电池,具有高效率、低成本的特点。
50
+
51
+ ## 权利要求
52
+
53
+ 1. 一种太阳能电池,其特征在于,包括:
54
+ - 基板
55
+ - 光吸收层
56
+ - 电极
57
+
58
+ 2. 根据权利要求1所述的太阳能电池,其特征在于,所述光吸收层采用钙钛矿材料。
59
+
60
+ 3. 根据权利要求1所述的太阳能电池,其特征在于,所述电极采用透明导电氧化物。
61
+
62
+ ## 说明书
63
+
64
+ 本发明公开了一种新型的太阳能电池,包括基板、光吸收层和电极。所述光吸收层采用钙钛矿材料,具有高效率、低成本的特点。所述电极采用透明导电氧化物,提高了光利用率。
65
+ """.strip()
66
+
67
+ self.sop_content = """
68
+ # 服务器维护SOP
69
+
70
+ ## 概述
71
+
72
+ 本文档描述了服务器日常维护的标准操作流程。
73
+
74
+ ## 步骤
75
+
76
+ 1. 检查服务器状态
77
+
78
+ 2. 更新系统补丁
79
+
80
+ 3. 备份关键数据
81
+
82
+ 4. 检查磁盘空间
83
+
84
+ 5. 检查内存使用情况
85
+
86
+ 6. 检查CPU负载
87
+
88
+ 7. 检查网络连接
89
+
90
+ 8. 生成维护报告
91
+
92
+ ## 检查项
93
+
94
+ - [x] 服务器状态正常
95
+ - [ ] 系统补丁已更新
96
+ - [x] 关键数据已备份
97
+ - [x] 磁盘空间充足
98
+ - [x] 内存使用正常
99
+ - [x] CPU负载正常
100
+ - [x] 网络连接正常
101
+ - [ ] 维护报告已生成
102
+
103
+ ## 风险评估
104
+
105
+ | 风险 | 等级 | 缓解措施 |
106
+ | --- | --- | --- |
107
+ | 系统宕机 | 高 | 提前通知用户,安排在非业务高峰期进行维护 |
108
+ | 数据丢失 | 高 | 多重备份,确保数据安全 |
109
+ | 网络中断 | 中 | 提前检查网络设备,确保网络稳定 |
110
+ """.strip()
111
+
112
+ self.paper_content = """
113
+ # 深度学习在图像处理中的应用
114
+
115
+ ## 摘要
116
+
117
+ 深度学习技术在图像处理领域取得了显著的成果,本文综述了深度学习在图像处理中的主要应用和最新进展。
118
+
119
+ ## 引言
120
+
121
+ 图像处理是计算机视觉的重要组成部分,传统的图像处理方法依赖于手工设计的特征提取器,而深度学习技术通过自动学习特征,显著提高了图像处理的性能。
122
+
123
+ ## 相关工作
124
+
125
+ 近年来,深度学习在图像处理领域的应用主要包括图像分类、目标检测、图像分割、图像生成等。
126
+
127
+ ## 方法
128
+
129
+ 本文采用文献综述的方法,系统分析了深度学习在图像处理中的应用。
130
+
131
+ ## 结果与讨论
132
+
133
+ 深度学习技术在图像处理领域取得了显著的成果,特别是在图像分类、目标检测等任务上,性能已经超过了人类专家。
134
+
135
+ ## 结论
136
+
137
+ 深度学习技术在图像处理领域具有广阔的应用前景,未来的研究方向包括模型轻量化、多模态融合等。
138
+
139
+ ## 参考文献
140
+
141
+ [1] Krizhevsky A, Sutskever I, Hinton G E. ImageNet classification with deep convolutional neural networks[J]. Communications of the ACM, 2017, 60(6): 84-90.
142
+ [2] Redmon J, Divvala S, Girshick R, et al. You only look once: Unified, real-time object detection[C]//Proceedings of the IEEE conference on computer vision and pattern recognition. 2016: 779-788.
143
+ """.strip()
144
+
145
+ def test_book_mode(self):
146
+ """测试Book模式"""
147
+ mode = BookMode()
148
+
149
+ # 测试处理功能
150
+ processed_content = mode.process(self.book_content)
151
+ self.assertIsInstance(processed_content, dict)
152
+ self.assertIn('book_structure', processed_content)
153
+ self.assertIn('toc', processed_content)
154
+ self.assertIn('book_metadata', processed_content)
155
+
156
+ # 测试验证功能
157
+ validation_result = mode.validate(self.book_content)
158
+ self.assertIsInstance(validation_result, dict)
159
+ self.assertIn('valid', validation_result)
160
+
161
+ def test_patent_mode(self):
162
+ """测试Patent模式"""
163
+ mode = PatentMode()
164
+
165
+ # 测试处理功能
166
+ processed_content = mode.process(self.patent_content)
167
+ self.assertIsInstance(processed_content, dict)
168
+
169
+ # 测试验证功能
170
+ validation_result = mode.validate(self.patent_content)
171
+ self.assertIsInstance(validation_result, dict)
172
+ self.assertIn('valid', validation_result)
173
+
174
+ def test_sop_mode(self):
175
+ """测试SOP模式"""
176
+ mode = SOPMode()
177
+
178
+ # 测试处理功能
179
+ processed_content = mode.process(self.sop_content)
180
+ self.assertIsInstance(processed_content, dict)
181
+
182
+ # 测试验证功能
183
+ validation_result = mode.validate(self.sop_content)
184
+ self.assertIsInstance(validation_result, dict)
185
+ self.assertIn('valid', validation_result)
186
+
187
+ def test_paper_mode(self):
188
+ """测试Paper模式"""
189
+ mode = PaperMode()
190
+
191
+ # 测试处理功能
192
+ processed_content = mode.process(self.paper_content)
193
+ self.assertIsInstance(processed_content, dict)
194
+
195
+ # 测试验证功能
196
+ validation_result = mode.validate(self.paper_content)
197
+ self.assertIsInstance(validation_result, dict)
198
+ self.assertIn('valid', validation_result)
199
+
200
+
201
+ if __name__ == '__main__':
202
+ unittest.main()
@@ -1,171 +0,0 @@
1
- """Markdown到DOCX格式转换器"""
2
-
3
- from typing import Any, Optional, Dict
4
- from docx import Document
5
- from docx.shared import Inches, Pt
6
- from docx.enum.text import WD_ALIGN_PARAGRAPH
7
- from docx.enum.style import WD_STYLE_TYPE
8
- from hos_m2f.converters.base_converter import BaseConverter
9
- import mistune
10
-
11
-
12
- class MDToDOCXConverter(BaseConverter):
13
- """Markdown到DOCX格式转换器"""
14
-
15
- def convert(self, input_content: str, options: Optional[Dict[str, Any]] = None) -> bytes:
16
- """将Markdown转换为DOCX
17
-
18
- Args:
19
- input_content: Markdown内容
20
- options: 转换选项
21
-
22
- Returns:
23
- bytes: DOCX文件的二进制数据
24
- """
25
- if options is None:
26
- options = {}
27
-
28
- # 创建文档
29
- doc = Document()
30
-
31
- # 设置默认样式
32
- self._setup_styles(doc)
33
-
34
- # 解析Markdown
35
- markdown = mistune.create_markdown(
36
- plugins=[
37
- 'url',
38
- 'abbr',
39
- 'def_list',
40
- 'footnotes',
41
- 'tables',
42
- 'task_lists',
43
- 'strikethrough',
44
- 'highlight',
45
- 'superscript',
46
- 'subscript'
47
- ]
48
- )
49
-
50
- # 自定义渲染器
51
- class DOCXRenderer(mistune.HTMLRenderer):
52
- def __init__(self, doc):
53
- super().__init__()
54
- self.doc = doc
55
- self.current_paragraph = None
56
- self.list_level = 0
57
- self.lists = []
58
-
59
- def paragraph(self, text):
60
- if text.strip():
61
- p = self.doc.add_paragraph()
62
- p.add_run(text)
63
- return ''
64
-
65
- def heading(self, text, level):
66
- if level == 1:
67
- self.doc.add_heading(text, level=0)
68
- else:
69
- self.doc.add_heading(text, level=level-1)
70
- return ''
71
-
72
- def list(self, text, ordered, level, start=None):
73
- self.list_level += 1
74
- self.lists.append(ordered)
75
- return ''
76
-
77
- def list_item(self, text, level):
78
- if text.strip():
79
- p = self.doc.add_paragraph(
80
- text,
81
- style='List Number' if self.lists[level-1] else 'List Bullet'
82
- )
83
- # 缩进
84
- for i in range(level-1):
85
- p.paragraph_format.left_indent += Inches(0.5)
86
- return ''
87
-
88
- def list_end(self, level):
89
- self.list_level -= 1
90
- if self.lists:
91
- self.lists.pop()
92
- return ''
93
-
94
- def table(self, text):
95
- # 简化处理,实际项目中需要更复杂的表格解析
96
- self.doc.add_paragraph('Table: ' + text[:100] + '...')
97
- return ''
98
-
99
- def image(self, src, alt='', title=None):
100
- try:
101
- # 简化处理,实际项目中需要处理本地和远程图片
102
- self.doc.add_paragraph(f'Image: {alt} ({src})')
103
- except Exception:
104
- pass
105
- return ''
106
-
107
- def link(self, link, text=None, title=None):
108
- if text:
109
- p = self.doc.add_paragraph()
110
- run = p.add_run(text)
111
- # 实际项目中需要添加超链接
112
- return ''
113
-
114
- def emphasis(self, text):
115
- if self.current_paragraph:
116
- run = self.current_paragraph.add_run(text)
117
- run.italic = True
118
- return ''
119
-
120
- def strong(self, text):
121
- if self.current_paragraph:
122
- run = self.current_paragraph.add_run(text)
123
- run.bold = True
124
- return ''
125
-
126
- def codespan(self, text):
127
- p = self.doc.add_paragraph()
128
- run = p.add_run(text)
129
- run.font.name = 'Courier New'
130
- return ''
131
-
132
- def block_code(self, code, lang=None):
133
- p = self.doc.add_paragraph()
134
- run = p.add_run(code)
135
- run.font.name = 'Courier New'
136
- p.paragraph_format.left_indent = Inches(0.5)
137
- return ''
138
-
139
- # 渲染Markdown
140
- renderer = DOCXRenderer(doc)
141
- markdown(input_content, renderer)
142
-
143
- # 保存为二进制数据
144
- import io
145
- output = io.BytesIO()
146
- doc.save(output)
147
- output.seek(0)
148
-
149
- return output.getvalue()
150
-
151
- def _setup_styles(self, doc):
152
- """设置文档样式"""
153
- styles = doc.styles
154
-
155
- # 设置正文样式
156
- normal_style = styles['Normal']
157
- font = normal_style.font
158
- font.name = 'Microsoft YaHei'
159
- font.size = Pt(12)
160
-
161
- # 设置标题样式
162
- for i in range(1, 6):
163
- heading_style = styles[f'Heading {i}']
164
- font = heading_style.font
165
- font.name = 'Microsoft YaHei'
166
- font.size = Pt(14 + (6 - i) * 2)
167
- font.bold = True
168
-
169
- def get_supported_formats(self) -> tuple:
170
- """获取支持的格式"""
171
- return ('markdown', 'docx')
File without changes
File without changes
File without changes
File without changes