使用ITextSharp提取和更新现有PDF中的链接

时间:2011-11-15 17:16:05

标签: c# c#-4.0 itextsharp

我需要将几个(阅读:很多)PDF文件发布到网上,但其中许多都有硬编码的文件://链接和指向非公共位置的链接。我需要阅读这些PDF并更新指向正确位置的链接。我已经开始使用itextsharp编写应用程序来读取目录和文件,找到PDF并遍历每个页面。我接下来要做的是找到链接,然后更新不正确的链接。

string path = "c:\\html";
DirectoryInfo rootFolder = new DirectoryInfo(path);

foreach (DirectoryInfo di in rootFolder.GetDirectories())
{
    // get pdf
    foreach (FileInfo pdf in di.GetFiles("*.pdf"))
    {
        string contents = string.Empty;
        Document doc = new Document();
        PdfReader reader = new PdfReader(pdf.FullName);

        using (MemoryStream ms = new MemoryStream())
        {
            PdfWriter writer = PdfWriter.GetInstance(doc, ms);
            doc.Open();

            for (int p = 1; p <= reader.NumberOfPages; p++)
            {
                byte[] bt = reader.GetPageContent(p);

            }
        }
    }
}

坦率地说,一旦我获得了页面内容,就iTextSharp而言,我对此非常感兴趣。我已经阅读了sourceforge上的itextsharp示例,但实际上并没有找到我想要的内容。

非常感谢任何帮助。

感谢。

3 个答案:

答案 0 :(得分:30)

如果你不了解PDF格式的内部和iText / iTextSharp的抽象/实现,那么这个有点复杂。您需要了解如何使用PdfDictionary个对象并按其PdfName键查找。完成后,您可以阅读official PDF spec并轻松浏览文档。如果您确实在意,我已将括号中的PDF规范的相关部分包含在适当的位置。

无论如何,PDF中的链接存储为注释(PDF Ref 12.5)。注释是基于页面的,因此您需要首先单独获取每个页面的注释数组。有许多不同类型的注释,因此您需要检查每个注释的SUBTYPE并查看它是否设置为LINK12.5.6.5)。每个链接 都应该有一个与之关联的ACTION字典(12.6.2),并且您想要检查操作的S键以查看它是什么类型的操作。这有很多可能的,链接特别可能是内部链接或打开文件链接或播放声音链接或其他东西(12.6.4.1)。您只查找URI类型的链接(请注意字母I而不是字母L)。 URI操作(12.6.4.7)有一个URI键,用于保存要导航到的实际地址。 (图像地图还有一个IsMap属性,我实际上无法想象有人使用它。)

呼。还在看?以下是针对iTextSharp 5.1.1.0的完整工作VS 2010 C#WinForms应用based on my post here。此代码主要做两件事:1)创建一个示例PDF,其中的链接指向Google.com,2)用链接替换该链接到bing.com。代码应该得到很好的评论,但随时可以提出任何问题。

using System;
using System.Text;
using System.Windows.Forms;
using iTextSharp.text;
using iTextSharp.text.pdf;
using System.IO;

namespace WindowsFormsApplication1
{
    public partial class Form1 : Form
    {

        //Folder that we are working in
        private static readonly string WorkingFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "Hyperlinked PDFs");
        //Sample PDF
        private static readonly string BaseFile = Path.Combine(WorkingFolder, "OldFile.pdf");
        //Final file
        private static readonly string OutputFile = Path.Combine(WorkingFolder, "NewFile.pdf");

        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            CreateSamplePdf();
            UpdatePdfLinks();
            this.Close();
        }

        private static void CreateSamplePdf()
        {
            //Create our output directory if it does not exist
            Directory.CreateDirectory(WorkingFolder);

            //Create our sample PDF
            using (iTextSharp.text.Document Doc = new iTextSharp.text.Document(PageSize.LETTER))
            {
                using (FileStream FS = new FileStream(BaseFile, FileMode.Create, FileAccess.Write, FileShare.Read))
                {
                    using (PdfWriter writer = PdfWriter.GetInstance(Doc, FS))
                    {
                        Doc.Open();

                        //Turn our hyperlink blue
                        iTextSharp.text.Font BlueFont = FontFactory.GetFont("Arial", 12, iTextSharp.text.Font.NORMAL, iTextSharp.text.BaseColor.BLUE);

                        Doc.Add(new Paragraph(new Chunk("Go to URL", BlueFont).SetAction(new PdfAction("http://www.google.com/", false))));

                        Doc.Close();
                    }
                }
            }
        }

        private static void UpdatePdfLinks()
        {
            //Setup some variables to be used later
            PdfReader R = default(PdfReader);
            int PageCount = 0;
            PdfDictionary PageDictionary = default(PdfDictionary);
            PdfArray Annots = default(PdfArray);

            //Open our reader
            R = new PdfReader(BaseFile);
            //Get the page cont
            PageCount = R.NumberOfPages;

            //Loop through each page
            for (int i = 1; i <= PageCount; i++)
            {
                //Get the current page
                PageDictionary = R.GetPageN(i);

                //Get all of the annotations for the current page
                Annots = PageDictionary.GetAsArray(PdfName.ANNOTS);

                //Make sure we have something
                if ((Annots == null) || (Annots.Length == 0))
                    continue;

                //Loop through each annotation

                foreach (PdfObject A in Annots.ArrayList)
                {
                    //Convert the itext-specific object as a generic PDF object
                    PdfDictionary AnnotationDictionary = (PdfDictionary)PdfReader.GetPdfObject(A);

                    //Make sure this annotation has a link
                    if (!AnnotationDictionary.Get(PdfName.SUBTYPE).Equals(PdfName.LINK))
                        continue;

                    //Make sure this annotation has an ACTION
                    if (AnnotationDictionary.Get(PdfName.A) == null)
                        continue;

                    //Get the ACTION for the current annotation
                    PdfDictionary AnnotationAction = (PdfDictionary)AnnotationDictionary.Get(PdfName.A);

                    //Test if it is a URI action
                    if (AnnotationAction.Get(PdfName.S).Equals(PdfName.URI))
                    {
                        //Change the URI to something else
                        AnnotationAction.Put(PdfName.URI, new PdfString("http://www.bing.com/"));
                    }
                }
            }

            //Next we create a new document add import each page from the reader above
            using (FileStream FS = new FileStream(OutputFile, FileMode.Create, FileAccess.Write, FileShare.None))
            {
                using (Document Doc = new Document())
                {
                    using (PdfCopy writer = new PdfCopy(Doc, FS))
                    {
                        Doc.Open();
                        for (int i = 1; i <= R.NumberOfPages; i++)
                        {
                            writer.AddPage(writer.GetImportedPage(R, i));
                        }
                        Doc.Close();
                    }
                }
            }
        }
    }
}

修改

我应该注意,这只会改变实际的链接。文档中的任何文本都不会更新。注释是在文本的顶部绘制的,但无论如何都没有真正与下面的文本相关联。这完全是另一个话题。

答案 1 :(得分:3)

注意如果Action是间接的,它将不会返回字典,您将收到错误:

PdfDictionary AnnotationAction = (PdfDictionary)AnnotationDictionary.Get(PdfName.A);

如果是可能的间接词典:

PdfDictionary Action = null;

//Get action directly or by indirect reference
PdfObject obj = Annotation.Get(PdfName.A);
if (obj.IsIndirect) {
    Action = PdfReader.GetPdfObject(obj);
} else {
    Action = (PdfDictionary)obj;
}

在这种情况下,您必须调查返回的字典以确定URI的找到位置。与间接/启动字典一样,URI位于/ F项中,类型为PRIndirectReference,其中/ Type为/ FileSpec,URI位于/ F

的值

答案 2 :(得分:2)

添加了处理间接和启动操作以及null注释字典的代码:

PdfReader r = new PdfReader(@"d:\kb2\" + f);
for (int i = 1; i <= r.NumberOfPages; i++) {
    //Get the current page
    var PageDictionary = r.GetPageN(i);

    //Get all of the annotations for the current page
    var Annots = PageDictionary.GetAsArray(PdfName.ANNOTS);

    //Make sure we have something
    if ((Annots == null) || (Annots.Length == 0))
        continue;
    foreach (var A in Annots.ArrayList) {
        var AnnotationDictionary = PdfReader.GetPdfObject(A) as PdfDictionary;
        if (AnnotationDictionary == null)
            continue;
        //Make sure this annotation has a link
        if (!AnnotationDictionary.Get(PdfName.SUBTYPE).Equals(PdfName.LINK))
            continue;

        //Make sure this annotation has an ACTION
        if (AnnotationDictionary.Get(PdfName.A) == null)
            continue;

        var annotActionObject = AnnotationDictionary.Get(PdfName.A);
        var AnnotationAction = (PdfDictionary)(annotActionObject.IsIndirect() ? PdfReader.GetPdfObject(annotActionObject) : annotActionObject); 

        var type = AnnotationAction.Get(PdfName.S);
        //Test if it is a URI action
        if (type.Equals(PdfName.URI)) {
            //Change the URI to something else
            string relativeRef = AnnotationAction.GetAsString(PdfName.URI).ToString();
            AnnotationAction.Put(PdfName.URI, new PdfString(url));
        } else if (type.Equals(PdfName.LAUNCH)) {
            //Change the URI to something else
            var filespec = AnnotationAction.GetAsDict(PdfName.F);
            string url = filespec.GetAsString(PdfName.F).ToString();
            AnnotationAction.Put(PdfName.F, new PdfString(url));
        }
    }
}
//Next we create a new document add import each page from the reader above
using (var output = File.OpenWrite(outputFile.FullName)) {
    using (Document Doc = new Document()) {
        using (PdfCopy writer = new PdfCopy(Doc, output)) {
            Doc.Open();
            for (int i = 1; i <= r.NumberOfPages; i++) {
                writer.AddPage(writer.GetImportedPage(r, i));
            }
            Doc.Close();
        }
    }
}
r.Close();