使用Python和HTMLDOC动态转换HTML为PDF

0 投票
2 回答
2405 浏览
提问于 2025-04-17 10:23

大约一年前,我为一个客户开发了一个Django应用程序。现在,他把这个应用程序转售给了一个超级保密的政府机构,他们甚至不告诉我这个机构的名字。

这个应用程序的一部分功能是使用一个叫xhtml2pdf(也叫pisa)的Python库动态生成PDF文件。可是,政府不喜欢这个库,他们也不告诉我原因,只说我必须使用HTMLDOC来生成PDF。

关于这个库的文档不多,但我从PHP的示例中看到,似乎可以通过命令行与它进行沟通,所以应该可以和Python一起使用。不过,我在把HTML传给HTMLDOC时遇到了困难。看起来HTMLDOC只接受文件,但我需要把动态生成的HTML作为字符串传过去。(或者先把HTML字符串写入一个临时文件,然后再把这个临时文件传给HTMLDOC)。

我以为使用StringIO可以解决这个问题,但我遇到了错误。以下是我的代码:

def render_to_pdf(template_src, context_dict):
    template = get_template(template_src)
    context = Context(context_dict)
    html  = template.render(context)
    result = StringIO.StringIO(html.encode("utf-8"))
    os.putenv("HTMLDOC_NOCGI", "1")

    #this line throws "[Errno 2] No such file or directory"
    htmldoc = subprocess.Popen("htmldoc -t pdf --quiet '%s'" % result, stdout=subprocess.PIPE).communicate()

    pdf = htmldoc[0]
    result.close()
    return HttpResponse(pdf, mimetype='application/pdf')

如果有任何想法、建议或帮助,我会非常感激。

谢谢。

更新

错误追踪信息:

Environment:


Request Method: GET
Request URL: (redacted)

Django Version: 1.3 alpha 1 SVN-14921
Python Version: 2.6.5
Installed Applications:
['django.contrib.auth',
 'django.contrib.contenttypes',
 'django.contrib.sessions',
 'django.contrib.sites',
 'django.contrib.messages',
 'django.contrib.admin',
 'application']
Installed Middleware:
('django.middleware.common.CommonMiddleware',
 'django.contrib.sessions.middleware.SessionMiddleware',
 'django.middleware.csrf.CsrfViewMiddleware',
 'django.contrib.auth.middleware.AuthenticationMiddleware',
 'django.contrib.messages.middleware.MessageMiddleware')


Traceback:
File "/usr/local/lib/python2.6/dist-packages/django/core/handlers/base.py" in get_response

  111. response = callback(request, *callback_args, **callback_kwargs)

File "/usr/local/lib/python2.6/dist-packages/django/contrib/auth/decorators.py" in _wrapped_view

  23. return view_func(request, *args, **kwargs)

File "/home/ascgov/application/views/pdf.py" in application_pdf

  90. 'user':owner})

File "/home/ascgov/application/views/pdf.py" in render_to_pdf

  53. htmldoc = subprocess.Popen("/usr/bin/htmldoc -t pdf --quiet '%s'" % result, stdout=subprocess.PIPE).communicate()

File "/usr/lib/python2.6/subprocess.py" in __init__

  633. errread, errwrite)

File "/usr/lib/python2.6/subprocess.py" in _execute_child

  1139. raise child_exception

Exception Type: OSError at /pdf/application/feed-filtr/
Exception Value: [Errno 2] No such file or directory

2 个回答

0

唉,这个要求真让人无奈,程序也糟糕透了。

看起来没有办法把要转换的内容作为命令行选项来传递。不过,它似乎是可以接受一个网址的。所以,理论上你可以传一个构造好的网址,指向同一个服务器上的一个视图,然后在那个视图里输出你渲染好的模板,这样HTMLDOC就可以从第一个视图中获取到内容了。不过要注意,这在开发服务器上是行不通的,因为开发服务器是单线程的,所以视图之间会一直互相等待。

3

首先,subprocess.Popen的第一个参数通常应该是一个列表(除非你同时传入shell=True)。出现No such file or directory的错误,很可能是因为系统上没有一个名为"htmldoc -t pdf --quiet '...的文件(它试图找到并运行这个完整字符串所表示的程序)。

其次,如果你给htmldoc一些html内容作为输入,它会把结果输出为pdf,这样就不需要临时文件了。

可以试试这个(未经测试):

htmldoc = subprocess.Popen(
  ['/usr/bin/htmldoc', '-t', 'pdf', '--webpage', '-'], 
  stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE
)
stdout, stderr = htmldoc.communicate(html)

注意:把/usr/bin/htmldoc替换成你系统上htmldoc的真实路径。

htmldoc程序中的-参数告诉它从标准输入读取。你会把你的html字符串(html)作为参数传给htmldoc的stdin,通过htmldoc.communicate调用。生成的pdf输出应该在stdout中,而其他消息或统计信息则在stderr中。

编辑:文档看起来有点奇怪,但内容还是挺多的。你可以试试一页的htmlpdf版本,或者手册页

另外,确保传给htmldoc进程的标准输入的是一个字符串或类似的东西。直接传递一个StringIO对象,如我之前的代码片段所暗示的,是行不通的。

撰写回答