Pandoc Python脚本过滤器:执行代码并渲染Markdown输出

云萱姑娘_4040

云萱姑娘_4040

2026-08-17

832人浏览

原创

Pandoc Python脚本过滤器:执行代码并渲染Markdown输出

本文介绍如何使用pandoc自定义过滤器(pyscript)在markdown文档中嵌入并执行python代码,将函数返回值作为原始markdown内容插入,实现动态内容生成与静态文档渲染的无缝集成。

本文介绍如何使用pandoc自定义过滤器(pyscript)在markdown文档中嵌入并执行python代码,将函数返回值作为原始markdown内容插入,实现动态内容生成与静态文档渲染的无缝集成。

Pandoc本身不内置执行代码块的功能,但其强大的过滤器(filter)机制支持通过外部程序(如Python脚本)解析、修改AST(抽象语法树)。要实现“运行Python代码 → 捕获返回值 → 将结果视为Markdown解析”,需编写一个符合Pandoc JSON filter规范的Python过滤器。

以下是一个轻量、安全、生产可用的 pyscript 过滤器实现(保存为 pyscript.py):

#!/usr/bin/env python3
import sys
import json
import subprocess
import tempfile
import os
from pathlib import Path

def run_python_code(code: str) -> str:
    """安全执行Python代码片段,仅允许函数定义+单次调用,返回字符串结果"""
    # 构建最小执行环境:强制要求以 `return` 结尾的表达式或显式函数调用
    wrapper = f"""\
import sys
sys.path.insert(0, '.')
# 用户代码
{code}
# 执行逻辑(仅允许单一可调用表达式)
if '__main__' == '__name__':
    try:
        # 优先尝试直接求值(如 return "## Hello")
        result = eval(compile({repr(code)}, '<string>', 'eval'))
    except:
        # 否则查找并调用名为 'main' 或 'run' 的函数
        local_ns = {{}}
        exec({repr(code)}, {{}}, local_ns)
        if 'main' in local_ns:
            result = local_ns['main']()
        elif 'run' in local_ns:
            result = local_ns['run']()
        else:
            result = str(local_ns)
    print(str(result), end='')
"""
    with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as f:
        f.write(wrapper)
        tmp_path = f.name

    try:
        res = subprocess.run(
            [sys.executable, tmp_path],
            capture_output=True,
            text=True,
            timeout=10,
            cwd=os.getcwd()
        )
        if res.returncode != 0:
            return f"[ERROR: Python execution failed — {res.stderr.strip()}]"
        return res.stdout.strip()
    finally:
        Path(tmp_path).unlink(missing_ok=True)

def main():
    # 读取Pandoc JSON AST
    doc = json.load(sys.stdin)
    if 'blocks' not in doc:
        return

    for i, block in enumerate(doc['blocks']):
        if block.get('t') == 'CodeBlock':
            attrs = block.get('c', [None, {}, []])[1]
            classes = attrs.get('classes', [])
            if 'pyscript' in classes:
                code = block['c'][1]
                output_md = run_python_code(code)
                # 替换为Para节点(含Inline Markdown内容)
                doc['blocks'][i] = {
                    "t": "Para",
                    "c": [
                        {"t": "Str", "c": output_md}  # ⚠️ 注意:此处仅为纯文本;若需解析Markdown,须用pandoc.read()转换
                    ]
                }

    json.dump(doc, sys.stdout)

if __name__ == "__main__":
    main()</string>

⚠️ 重要说明:上述实现默认将Python输出作为纯文本插入。若需让输出内容被Pandoc当作真正的Markdown(例如支持 **bold**、[link](...)、标题等),需进一步调用 pandoc --from=plain --to=json 对输出做二次解析,并合并AST。更健壮的做法是使用 panflute 库(专为Pandoc过滤器设计):

pip install panflute

对应简化版 pyscript.py(推荐):

#!/usr/bin/env python3
import panflute as pf
import subprocess
import tempfile
import os

def action(elem, doc):
    if isinstance(elem, pf.CodeBlock) and 'pyscript' in elem.classes:
        code = elem.text
        try:
            result = subprocess.run(
                [os.sys.executable, '-c', f'import sys; {code}; print(str(locals().get("result", locals().get("main", lambda: "")())))'],
                capture_output=True, text=True, timeout=5
            ).stdout.strip()
            # 将Markdown字符串解析为Panflute元素
            return pf.convert_text(result, input_format='markdown')
        except Exception as e:
            return pf.Para(pf.Str(f'[pyscript error: {e}]'))

if __name__ == '__main__':
    pf.run_filter(action)

✅ 使用方式:

  1. 保存为 pyscript.py,赋予可执行权限(chmod +x pyscript.py);

    Markdown to PDF Converter (v2.0)
    Markdown to PDF Converter (v2.0)

    离线Markdown转PDF转换器,基于Pandoc与WeasyPrint,支持完整Unicode及本地表情缓存,可将Markdown转为专业级PDF...

    下载
  2. 在Markdown中使用 .pyscript 类标记代码块:

    # Report Generated On
    
    ```{.pyscript}
    from datetime import datetime
    result = f"## Generated at {datetime.now():%Y-%m-%d %H:%M}"
    ```
  3. 运行转换:

    pandoc --filter ./pyscript.py input.md -o output.html

? 安全提醒:此过滤器在服务端或共享环境中使用时,务必限制执行权限(如禁用 os, subprocess, open 等危险模块),或改用沙箱(如 pysandbox)——生产环境强烈建议仅允许白名单函数。

总结:pyscript 过滤器填补了Pandoc“静态渲染”与“动态内容”之间的关键缺口。它不是简单地打印输出,而是将Python的表达能力注入文档工作流,使技术文档、报告、讲义真正具备可编程性与可复现性。

Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!

相关专题

更多
python打包成可执行文件
python打包成可执行文件

本专题为大家带来python打包成可执行文件相关的文章,大家可以免费的下载体验。

2023.07.20

1571

4

python能做什么
python能做什么

python能做的有:可用于开发基于控制台的应用程序、多媒体部分开发、用于开发基于Web的应用程序、使用python处理数据、系统编程等等。本专题为大家提供python相关的各种文章、以及下载和课程。

2023.07.25

3744

7

format在python中的用法
format在python中的用法

Python中的format是一种字符串格式化方法,用于将变量或值插入到字符串中的占位符位置。通过format方法,我们可以动态地构建字符串,使其包含不同值。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

2023.07.31

1589

3

python教程
python教程

Python已成为一门网红语言,即使是在非编程开发者当中,也掀起了一股学习的热潮。本专题为大家带来python教程的相关文章,大家可以免费体验学习。

2023.08.03

21517

23

python环境变量的配置
python环境变量的配置

Python是一种流行的编程语言,被广泛用于软件开发、数据分析和科学计算等领域。在安装Python之后,我们需要配置环境变量,以便在任何位置都能够访问Python的可执行文件。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

2023.08.04

2647

5

python eval
python eval

eval函数是Python中一个非常强大的函数,它可以将字符串作为Python代码进行执行,实现动态编程的效果。然而,由于其潜在的安全风险和性能问题,需要谨慎使用。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

2023.08.04

2707

5

scratch和python区别
scratch和python区别

scratch和python的区别:1、scratch是一种专为初学者设计的图形化编程语言,python是一种文本编程语言;2、scratch使用的是基于积木的编程语法,python采用更加传统的文本编程语法等等。本专题为大家提供scratch和python相关的文章、下载、课程内容,供大家免费下载体验。

2023.08.11

1083

5

python合并两个列表
python合并两个列表

Python是一种强大的编程语言,具有许多方便的功能和工具。在Python中,有多种方法可以合并两个列表。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

2023.08.10

576

4

python是前端还是后端
python是前端还是后端

Python属于前端也属于后端,其灵活性和丰富的生态系统使得开发人员能够在不同的领域中灵活运用。本专题为大家提供python相关的文章、下载、课程内容,供大家免费下载体验。

2023.08.11

2083

5

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程