usando ITextSharp para extraer y actualizar enlaces en un PDF existente

Necesito publicar varios (lea: muchos) archivos PDF en la web, pero muchos de ellos tienen archivos codificados: // enlaces y enlaces a ubicaciones no públicas. Necesito leer estos archivos PDF y actualizar los enlaces a las ubicaciones correctas. Empecé a escribir una aplicación usando itextsharp para leer los directorios y archivos, buscar los PDF y recorrer cada página. Lo que necesito hacer a continuación es encontrar los enlaces y luego actualizar los incorrectos.

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

Francamente, una vez que obtengo el contenido de la página estoy bastante perdido en esto cuando se trata de iTextSharp. He leído los ejemplos de itextsharp en sourceforge, pero realmente no encontré lo que estaba buscando.

Cualquier ayuda sería muy apreciada.

Gracias.

Este es un poco complicado si no conoce las partes internas del formato PDF y la abstracción / implementación de iText / iTextSharp. PdfDictionary comprender cómo usar objetos PdfDictionary y buscar cosas con su clave PdfName . Una vez que lo obtenga, puede leer las especificaciones oficiales de PDF y examinar un documento con bastante facilidad. Si le importa, he incluido las partes relevantes de la especificación PDF entre paréntesis, donde corresponda.

De todos modos, un enlace dentro de un PDF se almacena como una anotación ( PDF Ref 12.5 ). Las anotaciones se basan en páginas, por lo que primero debe obtener el conjunto de anotaciones de cada página individualmente. Hay un montón de diferentes tipos de anotaciones posibles, por lo que debe verificar el SUBTYPE cada uno y ver si está establecido en LINK ( 12.5.6.5 ). Cada enlace debe tener un diccionario de ACTION asociado ( 12.6.2 ) y usted desea verificar la tecla S la acción para ver qué tipo de acción es. Hay un montón de posibles para esto, los enlaces específicamente podrían ser enlaces internos o enlaces de archivos abiertos o reproducir enlaces de sonido u otra cosa ( 12.6.4.1 ). Solo busca enlaces que sean de tipo URI (tenga en cuenta la letra I y no la letra L ). Acciones URI ( 12.6.4.7 ) tienen una clave URI que contiene la dirección real para navegar. (También hay una propiedad IsMap para mapas de imágenes que no puedo imaginar que alguien use).

Uf. ¿Seguir leyendo? A continuación se muestra una aplicación completa VS 2010 C # WinForms basada en mi publicación dirigida a iTextSharp 5.1.1.0. Este código hace dos cosas principales: 1) Crear un PDF de muestra con un enlace que apunta a Google.com y 2) reemplaza ese enlace con un enlace a bing.com. El código debe ser muy bien comentado, pero no dude en hacer cualquier pregunta que pueda tener.

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

EDITAR

Debo señalar, esto solo cambia el enlace real. Cualquier texto dentro del documento no se actualizará. Las anotaciones se dibujan en la parte superior del texto, pero de todos modos no están vinculadas al texto debajo. Ese es otro tema completamente.

Si la acción es indirecta, no devolverá un diccionario y tendrá un error:

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

En los casos de posibles diccionarios indirectos:

 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; } 

En ese caso, debe investigar el diccionario devuelto para descubrir dónde se encuentra el URI. Al igual que con un diccionario indirecto / de inicio, el URI se encuentra en el elemento / F del tipo PRIndirectReference con / / siendo a / FileSpec y el URI ubicado en el valor de / F

Se agregó un código para tratar las acciones indirectas y de inicio y el diccionario de anotación nulo:

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