如何使用Python替换PDF中的文本?

11 浏览
0 Comments

如何使用Python替换PDF中的文本?

我从另一个帖子这里拿到了使用PyPDF2库解析和替换PDF文本的代码。帖子中给出的示例PDF被解析为PyPDF2.generic.DecodedStreamObject。我目前正在处理的PDF是公司提供给我的,使用了Microsoft Word的导出到PDF功能创建。这生成了一个PyPDF2.generic.EncodedStreamObject。经过探索,主要区别在于文本中的某些地方似乎存在字距调整。\n这给我在示例代码中带来了两个问题。首先,在main函数中的if len(contents) > 0:这一行似乎被错误地触发,并尝试使用EncodedStreamObject字典的键而不是EncodedStreamObject本身。为了解决这个问题,我注释掉了if块,并使用else块中的代码处理了这两种情况。\n第二个问题是,(我猜)字距标记破坏了我要替换的文本。我注意到并不是每一行都有字距调整,所以我假设字距标记并不是严格必要的,并尝试将其删除后的输出效果如何。文本的结构大致如下:[(Thi)4(s)-1(is t)2(ext)]。我将replace_text函数中的replaced_line = line这一行替换为replaced_line = \"[(\" + \"\".join(re.findall(r\'\\((.*?)\\)\', line)) + \")] TJ\"。这样保留了观察到的结构,同时允许搜索替换文本。我验证了这实际上替换了该行的文本。\n然而,这些更改都没有阻止代码执行,但输出的PDF似乎完全没有变化,尽管使用print语句检查替换后的行是否有新文本,代码似乎工作正常。起初我认为这是因为process_data函数中决定是否为Encoded或Decoded的if块。然而,我查看了库的实际源代码位于此处,发现如果对象是Encoded,则它会生成一个Decoded版本,if块会反映这一点。我唯一的其他想法是我在main函数中注释掉的if块并没有错误地捕获我的情况,而是以错误的方式处理它。我不知道如何修复它以正确处理它。\n我感觉离解决这个问题非常接近了,但我对接下来该做什么感到无助。我想在评论中问链接解决方案的发布者,但我没有足够的声望在SO上发表评论。有没有人对如何解决这个问题有任何线索?我并不特别关心使用哪个库或文件格式,但它必须保留我提供的Word文档的格式。我已经尝试过将其导出为HTML,但这会删除大部分格式和标题。我还尝试在Python中将.docx转换为PDF,但这要求我在机器上安装Word,这不是一个跨平台的解决方案。我还尝试了使用RTF,但据我所了解,该文件类型的解决方案是将其转换为.docx,然后再转换为PDF。\n以下是我目前拥有的全部代码:\n

import PyPDF2
import re
def replace_text(content, replacements=dict()):
    lines = content.splitlines()
    result = ""
    in_text = False
    for line in lines:
        if line == "BT":
            in_text = True
        elif line == "ET":
            in_text = False
        elif in_text:
            cmd = line[-2:]
            if cmd.lower() == 'tj':
                replaced_line = "[(" + "".join(re.findall(r'\((.*?)\)', line)) + ")] TJ"
                for k, v in replacements.items():
                    replaced_line = replaced_line.replace(k, v)
                result += replaced_line + "\n"
            else:
                result += line + "\n"
            continue
        result += line + "\n"
    return result
def process_data(obj, replacements):
    data = obj.getData()
    decoded_data = data.decode('utf-8')
    replaced_data = replace_text(decoded_data, replacements)
    encoded_data = replaced_data.encode('utf-8')
    if obj.decodedSelf is not None:
        obj.decodedSelf.setData(encoded_data)
    else:
        obj.setData(encoded_data)
pdf = PyPDF2.PdfFileReader("template.pdf")
# pdf = PyPDF2.PdfFileReader("sample.pdf")
writer = PyPDF2.PdfFileWriter()
replacements = {
    "some text": "replacement text"
}
for page in pdf.pages:
    contents = page.getContents()
    # if len(contents) > 0:
    #   for obj in contents:
    #       streamObj = obj.getObject()
    #       process_data(streamObj, replacements)
    # else:
    process_data(contents, replacements)
    writer.addPage(page)
with open("output.pdf", 'wb') as out_file:
    writer.write(out_file)

\n编辑:\n我已经在一定程度上找到了问题的源头。这一行obj.decodedSelf.setData(encoded_data)似乎没有正确地设置数据。在此行之后,我添加了以下代码:\n

print(encoded_data[:2000])
print("----------------------")
print(obj.getData()[:2000])

\n第一条打印语句与第二条打印语句不同,这显然是不应该的。为了真正测试这是否属实,我将每一行都替换为[()],我知道有很多行已经是这样的。但是,不论如何,我都无法弄清楚为什么这个函数调用未能产生任何持久的更改。\n编辑2:\n我进一步确定了问题的所在。在EncodedStreamObject的getData方法中,它返回self.decodedSelf.getData(),如果self.decodedSelf为True。然而,在执行obj.decodedSelf.setData(encoded_data)之后,如果我执行print(bool(obj.decodedSelf)),它将打印出False。这意味着当EncodedStreamObject被访问以写入PDF时,它会重新解析旧的PDF并覆盖self.decodedSelf对象!除非我进入并修复源代码,否则我不知道如何解决这个问题。\n编辑3:\n我设法让库使用具有替换内容的解码版本!在writer.addPage(page)之前插入page[PyPDF2.pdf.NameObject(\"/Contents\")] = contents.decodedSelf这一行,强制页面具有更新后的内容。不幸的是,我之前关于文本字距的假设是错误的。在替换文本后,我的一些文本神秘地从PDF中消失了。我猜这是因为格式不正确的原因。\n最终编辑:\n我觉得我应该把这个放在这里,以防其他人碰巧遇到这个问题。我从未真正成功地让它按预期工作。相反,我选择了一种模拟PDF的HTML/CSS解决方案。如果在HTML中添加以下样式标签,可以使其更像预期的PDF打印效果:\n


\n我建议对于任何想要做我所做的事情的人使用这个解决方案。有Python库可以将HTML转换为CSS,但它们不支持HTML5和CSS3(特别是它们不支持CSS的flex或grid)。您可以从任何浏览器将HTML页面打印为PDF以实现相同的效果。这明显不是问题的答案,所以我觉得最好将其作为编辑留下来。如果有人成功完成我所尝试的事情,请为我和其他人发布一个答案。

0