использование ITextSharp для извлечения и обновления ссылок в существующем PDF - PullRequest
16 голосов
/ 15 ноября 2011

Мне нужно опубликовать несколько (читай: много) файлов 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, но действительно не нашел то, что искал.

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

Спасибо.

Ответы [ 3 ]

30 голосов
/ 15 ноября 2011

Это немного сложно, если вы не знаете внутренности формата 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 для карт изображений, которое я даже не могу представить, чтобы кто-нибудь использовал.)

Вот так.Все еще читаете?Ниже полнофункциональное приложение Win 2010 C # для Win 2010 C # 1025 * на основе моего поста с таргетингом на 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();
                    }
                }
            }
        }
    }
}

РЕДАКТИРОВАТЬ

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

3 голосов
/ 23 февраля 2014

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

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. Как и в случае косвенного словаря / Launch, URI находится в элементе / F типа PRIndirectReference, а / Type - / FileSpec, а URI - в значении / F

2 голосов
/ 09 декабря 2014

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

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