Подтвердить что ты не робот

Используя ITextSharp для извлечения и обновления ссылок в существующем PDF файле

Мне нужно опубликовать несколько файлов PDF (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. Я прочитал примеры itextsharp на sourceforge, но на самом деле не нашел то, что искал.

Любая помощь будет принята с благодарностью.

Спасибо.

4b9b3361

Ответ 1

Это немного сложно, если вы не знаете внутренности формата PDF и абстракции/реализации iText/iTextSharp. Вам нужно понять, как использовать объекты PdfDictionary и искать вещи с помощью клавиши PdfName. Как только вы получите это, вы можете прочитать официальную спецификацию PDF и легко вытащить документ. Если вам все равно, я включил соответствующие части спецификации PDF в круглые скобки, где это применимо.

В любом случае ссылка в PDF сохраняется как аннотация (PDF Ref 12.5). Аннотации основаны на страницах, поэтому вам нужно сначала получить каждый блок аннотации каждой страницы. Там есть куча различных возможных типов аннотаций, поэтому вам нужно проверить каждый SUBTYPE и посмотреть, установлен ли его набор на LINK (12.5.6.5). Каждая ссылка должна иметь словарь ACTION, связанный с ним (12.6.2), и вы хотите проверить действие S, чтобы узнать, какой тип действия он имеет. Там может быть множество возможных, ссылка может быть внутренними ссылками или открывать ссылки на файлы или воспроизводить звуковые ссылки или что-то еще (12.6.4.1). Вы ищете только ссылки типа URI (обратите внимание на букву I, а не на букву L). Действия URI (12.6.4.7) имеют ключ URI, который содержит фактический адрес для перехода к. (Также есть свойство IsMap для карт изображений, которые я не могу себе представить никому, использующим.)

Уф. Все еще читаете? Ниже приведено полное приложение VS 2010 С# WinForms app на основе моего сообщения здесь, нацеленное на iTextSharp 5.1.1.0. Этот код выполняет две основные функции: 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();
                    }
                }
            }
        }
    }
}

ИЗМЕНИТЬ

Я должен отметить, что это только изменяет фактическую ссылку. Любой текст внутри документа не будет обновляться. Аннотации рисуются поверх текста, но на самом деле не привязаны к тексту под ним. Это еще одна тема.

Ответ 2

Отмечено, что действие косвенное, оно не вернет словарь, и вы получите сообщение об ошибке:

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, являющимся a/FileSpec, и URI, находящимся в значении/F

Ответ 3

Добавлен код для работы с косвенными и запускающими действиями и нулевым аннотационным словарем:

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();