使用iTextSharp多次向PDF文件写入字段

3
我有一个PDF文档,包含3个字段txt_FirstNametxt_MiddleNametxt_LastName,我使用向其中写入内容。我有一个循环来创建输出文件、写入数据并关闭文件。循环第一次写入文件时,会写入名字和中间名。循环第二次时,将会写入名字、中间名和姓氏。
问题是,在第二次循环中写入姓氏时,名字和中间名消失了。
目标是多次向同一个PDF文档写入内容。
下载PDF模板:https://www.scribd.com/document/412586469/Testing-Doc
    public static string templatePath = "C:\\temp\\template.pdf";
    public static string OutputPath = "C:\\Output\\";

    private static void Fill_PDF()
    {
        string outputFile = "output.pdf";
        int counter = 1;

        for (int i = 0; i < 2; i++)
        {
            PdfStamper pdfStamper;
            PdfReader reader;

            reader = new PdfReader(File.ReadAllBytes(templatePath));
            PdfReader.unethicalreading = true;

            if (File.Exists(OutputPath + outputFile))
            {
                pdfStamper = new PdfStamper(reader, new FileStream(OutputPath + outputFile,
                                                        FileMode.Append, FileAccess.Write));
            }
            else
            {
                pdfStamper = new PdfStamper(reader, new FileStream(OutputPath + outputFile,
                                                        FileMode.Create));
            }
            AcroFields pdfFormFields = pdfStamper.AcroFields;

            if (counter == 1)
            {
                pdfFormFields.SetField("txt_FirstName", "Scooby");
                pdfFormFields.SetField("txt_MiddleName", "Dooby");
                counter++;
            }
            else if (counter == 2)
            {
                pdfFormFields.SetField("txt_LastName", "Doo");
            }
            pdfStamper.Close();
        }
    }
3个回答

6
这似乎是一个简单的错误。第一次循环时,您加载空模板并编写名字和中间名。第二次循环时,您再次加载空模板,并只将姓氏写入其中,然后保存到同一文件名,覆盖它。如果在第二次循环期间,您想要加载已经包含名字和中间名的文件,则必须加载您第一次写入的输出文件,而不是再次加载空模板。或者如果您想再次加载空模板,则在if (counter == 2)子句中,您需要编写所有3个名称,而不仅仅是最后一个名称。
我重现了您的错误,并使其工作。这是我描述的第一种解决方案的代码(对您的代码进行了轻微修改):
    public static string templatePath = "C:\\temp\\template.pdf";
    public static string OutputPath = "C:\\temp\\output\\";

    private static void Fill_PDF()
    {
        string outputFile = "output.pdf";
        int counter = 1;

        for (int i = 0; i < 2; i++)
        {
            PdfStamper pdfStamper;
            PdfReader reader = null;

            /********** here's the changed part */
            if (counter == 1)
            {
                reader = new PdfReader(File.ReadAllBytes(templatePath));
            } else if (counter == 2)
            {
                reader = new PdfReader(File.ReadAllBytes(OutputPath + outputFile));
            }
            /************ end changed part */

            PdfReader.unethicalreading = true;

            if (File.Exists(OutputPath + outputFile))
            {
                pdfStamper = new PdfStamper(reader, new FileStream(OutputPath + outputFile,
                    FileMode.Append, FileAccess.Write));
            }
            else
            {
                pdfStamper = new PdfStamper(reader, new FileStream(OutputPath + outputFile,
                    FileMode.Create));
            }
            AcroFields pdfFormFields = pdfStamper.AcroFields;

            if (counter == 1)
            {
                pdfFormFields.SetField("txt_FirstName", "Scooby");
                pdfFormFields.SetField("txt_MiddleName", "Dooby");
                counter++;
            }
            else if (counter == 2)
            {
                pdfFormFields.SetField("txt_LastName", "Doo");
            }
            pdfStamper.Close();
        }
    }

你的代码看起来很不错,按照要求返回了结果。但是有一件事情我不太明白,就是在计数器2中他是如何得到名字和姓氏的。如果他每次都从循环中写入空模板,那么他也应该失去名字。如果有人能解释一下会更好。谢谢。 - Mdyahiya
是的,名字也丢失了,不确定他在哪里说了不同的话。 - Nick Garyu
抱歉,你是对的。感谢指出。我从阅读中误解了。问题是,在循环到第二次并写入姓氏时,名字和中间名消失了。 - Mdyahiya

1

这段代码存在两个主要问题。@Nick在他的回答中已经指出了第一个问题:如果在第二次遍历中,您想要编辑包含第一次遍历更改的文档版本,则必须将第一次遍历的输出文档作为第二次遍历的输入,而不是再次使用原始模板。他还提供了修复此问题的代码。

第二个问题位于此处:

if (File.Exists(OutputPath + outputFile))
{
    pdfStamper = new PdfStamper(reader, new FileStream(OutputPath + outputFile,
                                            FileMode.Append, FileAccess.Write));
}
else
{
    pdfStamper = new PdfStamper(reader, new FileStream(OutputPath + outputFile,
                                            FileMode.Create));
}

如果输出文件已经存在,你应该将你的PdfStamper的输出附加到它上面。这是错误的!PdfStamper的输出已经包含了原始PDF文件(来自PdfReader)的内容,只要它们没有被更改。因此,你的代码实际上会产生第一遍完整输出PDF和第二遍完整输出PDF的连接。
PDF是一种二进制格式,像这样连接文件不会产生有效的PDF文件。因此,加载你的最终结果的PDF查看器尝试修复这个双重PDF,假设它是单个PDF。结果可能或可能不像你想要的那样。
为了解决第二个问题,只需用else分支的内容替换上面的if{...}else{...}即可:
pdfStamper = new PdfStamper(reader, new FileStream(OutputPath + outputFile,
                                        FileMode.Create));

(FileMode.Create被定义为

指定操作系统应创建一个新文件。如果该文件已经存在,则将其覆盖。这需要Write权限。 FileMode.Create相当于请求如果文件不存在,使用CreateNew;否则,使用Truncate。如果文件已经存在但是是隐藏文件,则会抛出一个UnauthorizedAccessException异常。

因此,如果有一个文件,它也会执行所需的操作。

通过运行带有Append的代码,您可以看到输出文件会在几次运行后增长并超出需要的范围。此外,如果您在Adobe Reader中打开该文件并再次关闭,则Adobe Reader会提示保存更改;更改是修复工作。


你可能听说过PDF的增量更新,其中更改被追加到原始PDF中。但这与简单的连接不同,结果中的修订版是特别链接的,并且偏移量始终从第一个修订版的开头计算,而不是从当前修订版的开头计算。此外,增量更新应仅包含已更改的对象。
iText包含一个具有4个参数的PdfStamper构造函数,其中包括最终的布尔参数append。使用该构造函数并将append设置为true使iText创建增量更新。但即使在这里,您也不使用FileMode.Append...

-1

问题出在第二次迭代中再次使用模板文件。

第一次迭代:按预期正常工作!

第二次迭代:您正在读取相同的文件并仅写入最后一个名称。最终,第一次迭代创建的输出文件将被替换。

修复方法:在确定输出文件是否存在于该位置之后,选择要读取的文件源,如下所示。这应该解决问题。我亲自检查过,它有效!

if (File.Exists(OutputPath + outputFile))
{
    reader = new PdfReader(File.ReadAllBytes(OutputPath + outputFile));
    pdfStamper = new PdfStamper(reader, new FileStream(OutputPath + outputFile,
                                                            FileMode.Append, FileAccess.Write));
}
else
{
    reader = new PdfReader(File.ReadAllBytes(templatePath));
    pdfStamper = new PdfStamper(reader, new FileStream(OutputPath + outputFile,
                                                            FileMode.Create));
}

A 这个问题已经在Nick的回答中描述过了。 B 你的解决方案有一个缺点,如果在例程开始时已经存在一个(可能完全不相关的)PDF文件在OutputPath + outputFile,那么它将无法正常工作:在这种情况下,模板可能会被完全忽略!相比之下,Nick的解决方案始终从模板开始。(他可能应该使用标志或仅检查i而不是计数器,但这是微调)。 C 那个问题只是原始代码中两个主要问题之一。 - mkl

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接