markdown_pdf.py 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. import os
  2. import subprocess
  3. from pathlib import Path
  4. import tempfile
  5. import shutil
  6. # 设置pandoc路径
  7. PANDOC_PATH = r"D:\work\pandoc-3.6.4-windows-x86_64\pandoc-3.6.4\pandoc.exe"
  8. def check_dependencies():
  9. """检查必要的依赖是否已安装"""
  10. # 检查 pandoc
  11. if not os.path.exists(PANDOC_PATH):
  12. raise Exception(f"未找到 pandoc,请检查路径是否正确: {PANDOC_PATH}")
  13. print(f"找到 pandoc: {PANDOC_PATH}")
  14. # 测试 pandoc 命令
  15. try:
  16. result = subprocess.run([PANDOC_PATH, '--version'], capture_output=True, text=True, encoding='utf-8')
  17. print(f"Pandoc 版本信息:\n{result.stdout}")
  18. except Exception as e:
  19. print(f"测试 pandoc 命令时出错: {str(e)}")
  20. raise
  21. # 检查 xelatex
  22. if shutil.which('xelatex') is None:
  23. raise Exception("未找到 xelatex,请先安装 MiKTeX:https://miktex.org/download")
  24. print("找到 xelatex")
  25. # 检查 pdflatex
  26. if shutil.which('pdflatex') is None:
  27. print("警告:未找到 pdflatex")
  28. else:
  29. print("找到 pdflatex")
  30. def create_latex_template():
  31. """创建支持中文的LaTeX模板"""
  32. template = r"""\documentclass{article}
  33. \usepackage[UTF8]{ctex}
  34. \usepackage{listings}
  35. \usepackage{color}
  36. \usepackage{enumitem}
  37. \usepackage{booktabs}
  38. \usepackage{longtable}
  39. \usepackage{hyperref}
  40. \usepackage{amsmath}
  41. % 定义tightlist命令
  42. \newcommand{\tightlist}{%
  43. \setlength{\itemsep}{0pt}\setlength{\parskip}{0pt}}
  44. \lstset{
  45. basicstyle=\ttfamily,
  46. breaklines=true,
  47. frame=single,
  48. numbers=left,
  49. numberstyle=\tiny,
  50. keywordstyle=\color{blue},
  51. commentstyle=\color{green!60!black},
  52. stringstyle=\color{red}
  53. }
  54. \begin{document}
  55. $body$
  56. \end{document}"""
  57. # 使用临时文件
  58. with tempfile.NamedTemporaryFile(suffix='.tex', delete=False, mode='w', encoding='utf-8') as f:
  59. f.write(template)
  60. return f.name
  61. def markdown_to_docx(markdown_file: str, output_docx: str = None) -> str:
  62. """
  63. 将Markdown文件转换为DOCX文件
  64. Args:
  65. markdown_file (str): Markdown文件的路径
  66. output_docx (str, optional): 输出的DOCX文件路径。如果为None,则使用与输入文件相同的名称
  67. Returns:
  68. str: 生成的DOCX文件路径
  69. """
  70. # 获取绝对路径
  71. markdown_file = os.path.abspath(markdown_file)
  72. print(f"输入文件绝对路径: {markdown_file}")
  73. # 检查输入文件是否存在
  74. if not os.path.exists(markdown_file):
  75. raise FileNotFoundError(f"Markdown文件不存在: {markdown_file}")
  76. print(f"输入文件存在: {markdown_file}")
  77. # 如果没有指定输出文件,则使用与输入文件相同的名称
  78. if output_docx is None:
  79. output_docx = os.path.splitext(markdown_file)[0] + '.docx'
  80. else:
  81. output_docx = os.path.abspath(output_docx)
  82. print(f"输出DOCX文件路径: {output_docx}")
  83. # 确保输出目录存在
  84. output_dir = os.path.dirname(output_docx)
  85. if not os.path.exists(output_dir):
  86. os.makedirs(output_dir)
  87. print(f"创建输出目录: {output_dir}")
  88. try:
  89. # 使用pandoc将markdown转换为docx
  90. cmd = [
  91. PANDOC_PATH,
  92. markdown_file,
  93. '-o',
  94. output_docx,
  95. '--standalone', # 生成独立的文档
  96. '--wrap=preserve', # 保持原始换行
  97. '--highlight-style=tango', # 代码高亮样式
  98. '--metadata', 'lang=zh-CN' # 设置文档语言
  99. ]
  100. print(f"\n执行转换命令: {' '.join(cmd)}")
  101. # 使用utf-8编码执行命令
  102. result = subprocess.run(cmd, capture_output=True, text=True, encoding='utf-8')
  103. print(f"命令输出: {result.stdout}")
  104. if result.stderr:
  105. print(f"命令警告: {result.stderr}")
  106. # 检查DOCX是否生成
  107. if os.path.exists(output_docx):
  108. print(f"DOCX文件已成功生成: {output_docx}")
  109. return output_docx
  110. else:
  111. raise Exception(f"DOCX文件未生成: {output_docx}")
  112. except subprocess.CalledProcessError as e:
  113. print(f"命令执行错误: {e}")
  114. print(f"错误输出: {e.output if hasattr(e, 'output') else '无输出'}")
  115. raise Exception(f"转换过程中出错: {str(e)}")
  116. except Exception as e:
  117. print(f"发生错误: {str(e)}")
  118. raise Exception(f"发生未知错误: {str(e)}")
  119. def markdown_to_pdf(markdown_file: str, output_pdf: str = None) -> str:
  120. """
  121. 将Markdown文件转换为PDF文件
  122. Args:
  123. markdown_file (str): Markdown文件的路径
  124. output_pdf (str, optional): 输出的PDF文件路径。如果为None,则使用与输入文件相同的名称
  125. Returns:
  126. str: 生成的PDF文件路径
  127. """
  128. # 检查依赖
  129. check_dependencies()
  130. # 获取绝对路径
  131. markdown_file = os.path.abspath(markdown_file)
  132. print(f"输入文件绝对路径: {markdown_file}")
  133. # 检查输入文件是否存在
  134. if not os.path.exists(markdown_file):
  135. raise FileNotFoundError(f"Markdown文件不存在: {markdown_file}")
  136. print(f"输入文件存在: {markdown_file}")
  137. # 如果没有指定输出文件,则使用与输入文件相同的名称
  138. if output_pdf is None:
  139. output_pdf = os.path.splitext(markdown_file)[0] + '.pdf'
  140. else:
  141. output_pdf = os.path.abspath(output_pdf)
  142. print(f"输出PDF文件路径: {output_pdf}")
  143. # 确保输出目录存在
  144. output_dir = os.path.dirname(output_pdf)
  145. if not os.path.exists(output_dir):
  146. os.makedirs(output_dir)
  147. print(f"创建输出目录: {output_dir}")
  148. try:
  149. # 创建支持中文的LaTeX模板
  150. latex_template = create_latex_template()
  151. print(f"LaTeX模板文件: {latex_template}")
  152. # 使用pandoc直接将markdown转换为pdf
  153. cmd = [
  154. PANDOC_PATH,
  155. markdown_file,
  156. '-o',
  157. output_pdf,
  158. '--pdf-engine=xelatex',
  159. f'--template={latex_template}',
  160. '--listings',
  161. '--highlight-style=tango',
  162. '--verbose'
  163. ]
  164. print(f"\n执行转换命令: {' '.join(cmd)}")
  165. # 使用utf-8编码执行命令
  166. result = subprocess.run(cmd, capture_output=True, text=True, encoding='utf-8')
  167. print(f"命令输出: {result.stdout}")
  168. if result.stderr:
  169. print(f"命令警告: {result.stderr}")
  170. # 检查PDF是否生成
  171. if os.path.exists(output_pdf):
  172. print(f"PDF文件已成功生成: {output_pdf}")
  173. return output_pdf
  174. else:
  175. raise Exception(f"PDF文件未生成: {output_pdf}")
  176. except subprocess.CalledProcessError as e:
  177. print(f"命令执行错误: {e}")
  178. print(f"错误输出: {e.output if hasattr(e, 'output') else '无输出'}")
  179. raise Exception(f"转换过程中出错: {str(e)}")
  180. except Exception as e:
  181. print(f"发生错误: {str(e)}")
  182. raise Exception(f"发生未知错误: {str(e)}")
  183. finally:
  184. # 清理临时文件
  185. if os.path.exists(latex_template):
  186. try:
  187. os.remove(latex_template)
  188. print(f"已删除临时模板文件: {latex_template}")
  189. except Exception as e:
  190. print(f"删除临时文件时出错: {str(e)}")
  191. if __name__ == "__main__":
  192. # 使用示例
  193. try:
  194. # 获取当前脚本所在目录
  195. current_dir = os.path.dirname(os.path.abspath(__file__))
  196. print(f"当前工作目录: {current_dir}")
  197. # 示例:转换当前目录下的test.md文件
  198. input_file = os.path.join(current_dir, "test.md")
  199. # 转换为DOCX
  200. docx_file = os.path.join(current_dir, "output.docx")
  201. print(f"\n开始转换为DOCX...")
  202. docx_path = markdown_to_docx(input_file, docx_file)
  203. print(f"DOCX转换成功!")
  204. print(f"DOCX文件已保存至: {docx_path}")
  205. # 转换为PDF
  206. pdf_file = os.path.join(current_dir, "output.pdf")
  207. print(f"\n开始转换为PDF...")
  208. pdf_path = markdown_to_pdf(input_file, pdf_file)
  209. print(f"PDF转换成功!")
  210. print(f"PDF文件已保存至: {pdf_path}")
  211. except Exception as e:
  212. print(f"转换失败: {str(e)}")