Un PDF puede contener otros archivos dentro de él. Puede ser una cartera que agrupa contratos y hojas de cálculo, o un informe con sus datos de origen adjuntos. Cuando extraes esos archivos en código, deseas que cada uno se guarde con el nombre que le dio su autor, no como attachment1.bin. Este tutorial muestra cómo extraer archivos incrustados de un PDF con sus nombres originales en C# usando Aspose.PDF for .NET. Abrirás un PDF, recorrerás sus archivos incrustados, resolverás el nombre de cada archivo con FileSpecification.GetFileName y escribirás el contenido en una carpeta.

Dónde un PDF almacena el nombre de un archivo incrustado

Cada archivo incrustado se describe mediante una especificación de archivo, y una especificación de archivo puede registrar el nombre en más de un lugar. Existe un nombre de archivo Unicode, un nombre de archivo estándar y la clave bajo la cual el archivo se enumera en la colección de archivos incrustados del documento. Los productores de PDF no completan esto de manera consistente, y algunos omiten el nombre por completo.

Aspose.PDF for .NET proporciona la propiedad FileSpecification.GetFileName para manejar esto. Resuelve el nombre a partir del nombre Unicode, el nombre de archivo regular o la clave de la colección, lo que esté disponible, de modo que aún obtenga un nombre utilizable cuando falta el nombre Unicode. Puede pasar un nombre de respaldo para los archivos que no tengan ningún nombre. Un segundo parámetro opcional controla si se permite un nombre vacío cuando no hay nombre de archivo disponible. Obtiene una única llamada en lugar de su propia lógica para leer los diccionarios PDF.

Nota: Los ejemplos en este artículo usan solo el argumento de reserva.

El Document.EmbeddedFiles contiene los archivos adjuntos a nivel de documento, lo que incluye los archivos en una cartera PDF. Los archivos adjuntos a una página específica mediante una anotación de archivo adjunto (normalmente mostrada como un ícono de clip) se almacenan en la anotación; Extraer archivos adjuntos a páginas cubre esos casos.

Usando Aspose.PDF para extraer archivos incrustados de un PDF

Install-Package Aspose.PDF

O con la CLI de .NET:

dotnet add package Aspose.PDF
  • Un PDF que contiene archivos incrustados. El ejemplo usa una cartera llamada PDFPortfolio.pdf.

Sin una licencia, Aspose.PDF se ejecuta en modo de evaluación. Para probar sin restricciones de evaluación, solicite una licencia temporal gratuita.

Extraer cada archivo incrustado con su nombre original

El siguiente método abre el PDF, guarda cada archivo incrustado en una carpeta de salida con su nombre resuelto y muestra la ruta de cada archivo que escribe.

using System;
using System.IO;
using Aspose.Pdf;

public static class EmbeddedFileExtractor
{
    public static void ExtractEmbeddedFiles(string inputPdf, string outputDir)
    {
        Directory.CreateDirectory(outputDir);

using (var document = new Document(inputPdf))
        {
            foreach (FileSpecification fileSpec in document.EmbeddedFiles)
            {
                // Resolve the stored name, then keep only the file name part.
                string fileName = Path.GetFileName(fileSpec.GetFileName("attachment.bin"));
                string outputPath = Path.Combine(outputDir, fileName);

using (var output = new FileStream(outputPath, FileMode.Create))
                {
                    fileSpec.Contents.CopyTo(output);
                }

Console.WriteLine($"Saved: {outputPath}");
            }
        }
    }
}

Lláelo con la ruta del PDF y la carpeta donde desea los archivos:

EmbeddedFileExtractor.ExtractEmbeddedFiles("PDFPortfolio.pdf", "ExtractedFiles");

Qué hace cada paso

Abrir el documento. new Document(inputPdf) carga el PDF. El bloque using libera el controlador del archivo cuando finaliza el procesamiento, incluso si se lanza una excepción.

Recorra los archivos adjuntos. document.EmbeddedFiles devuelve un FileSpecification para cada archivo incrustado a nivel de documento.

Resolver el nombre. fileSpec.GetFileName("attachment.bin") devuelve el nombre almacenado, o attachment.bin si el PDF no lo proporciona. Elija una alternativa que tenga sentido para su aplicación.

Eliminar segmentos de carpeta. Un nombre almacenado puede incluir una ruta como reports/2026/q3.xlsx, o un valor manipulado como ..\..\startup.bat. Pasar el resultado a través de Path.GetFileName lo reduce a q3.xlsx o startup.bat, de modo que el archivo no pueda escribirse fuera de outputDir. Mantenga este paso incluso si confía en los PDFs que procesa hoy.

Escriba el contenido. fileSpec.Contents es una secuencia de los datos del archivo incrustado. CopyTo lo escribe en el nuevo archivo sin ninguna conversión.

Resultado esperado

La carpeta de salida contiene un archivo para cada adjunto a nivel de documento, nombrado tal como se almacenó en el PDF. Para una cartera que contiene una hoja de cálculo y un documento de Word, la salida de la consola se ve similar a esto:

Saved: ExtractedFiles\budget-2026.xlsx
Saved: ExtractedFiles\service-agreement.docx

Los nombres de archivo dependen de su PDF. Los archivos extraídos contienen los mismos datos que se incrustaron, por lo que cada uno se abre en la aplicación que maneja su formato.

Manejar nombres duplicados e inseguros

El ejemplo básico tiene tres puntos débiles que encontrarás con PDFs del mundo real:

  • Dos archivos adjuntos pueden compartir un nombre, y FileMode.Create sobrescribe silenciosamente el primero. Esto también ocurre cuando varios archivos adjuntos no tienen nombre y todos recurren a attachment.bin.
  • Un nombre que es válido en un sistema operativo puede contener caracteres que son inválidos en otro, como : en Windows.
  • Un nombre almacenado que termina con un separador de ruta deja a Path.GetFileName con una cadena vacía.

Este asistente maneja los tres casos. Reemplaza los caracteres no válidos, recurre a un valor predeterminado cuando el nombre está vacío y agrega un contador cuando el archivo ya existe:

private static string GetSafeOutputPath(string outputDir, string resolvedName, string fallbackName)
{
    string fileName = Path.GetFileName(resolvedName);

foreach (char c in Path.GetInvalidFileNameChars())
    {
        fileName = fileName.Replace(c, '_');
    }

if (string.IsNullOrWhiteSpace(fileName))
    {
        fileName = fallbackName;
    }

string baseName = Path.GetFileNameWithoutExtension(fileName);
    string extension = Path.GetExtension(fileName);
    string outputPath = Path.Combine(outputDir, fileName);

int counter = 1;
    while (File.Exists(outputPath))
    {
        outputPath = Path.Combine(outputDir, $"{baseName} ({counter}){extension}");
        counter++;
    }

return outputPath;
}

En el bucle de extracción, reemplace las dos líneas que construyen fileName y outputPath con:

string outputPath = GetSafeOutputPath(outputDir, fileSpec.GetFileName("attachment.bin"), "attachment.bin");

Debido a que el asistente verifica la existencia de archivos, ejecutar la extracción dos veces en la misma carpeta produce copias numeradas en lugar de sobrescribir. Vacía la carpeta primero si deseas un resultado nuevo en cada ejecución.

Extraer archivos adjuntos a las páginas

Si EmbeddedFiles está vacío pero puedes ver un ícono de clip en un visor de PDF, el archivo probablemente está adjunto mediante una anotación de adjunto de archivo en una página. La anotación contiene su propio FileSpecification en la propiedad File, por lo que se aplica la misma resolución de nombres y el mismo asistente. Este método revisa cada página y guarda cada archivo adjunto en la carpeta de salida:

using Aspose.Pdf.Annotations;

public static void ExtractPageAttachments(string inputPdf, string outputDir)
{
    Directory.CreateDirectory(outputDir);

using (var document = new Document(inputPdf))
    {
        foreach (Page page in document.Pages)
        {
            foreach (Annotation annotation in page.Annotations)
            {
                if (annotation is FileAttachmentAnnotation attachment && attachment.File != null)
                {
                    string outputPath = GetSafeOutputPath(outputDir, attachment.File.GetFileName("attachment.bin"), "attachment.bin");

using (var output = new FileStream(outputPath, FileMode.Create))
                    {
                        attachment.File.Contents.CopyTo(output);
                    }

Console.WriteLine($"Saved: {outputPath}");
                }
            }
        }
    }
}

Para recopilar cada adjunto en un PDF, ejecute ambos métodos con la misma carpeta de salida. Si un PDF hace referencia al mismo archivo desde ambos lugares, GetSafeOutputPath guarda la segunda copia con un nombre numerado en lugar de sobrescribir la primera.

Próximos pasos

Para profundizar en los archivos adjuntos, como agregar, eliminar o enumerarlos sin extraer, consulte la Aspose.PDF for .NET documentación. La FileSpecification referencia de clase enumera las propiedades de otro nombre y descripción que puede leer de cada archivo incrustado. Si un PDF no se extrae como se esperaba, publique un ejemplo en el foro de soporte de Aspose.PDF.

Preguntas frecuentes

  1. ¿Cómo elige un nombre FileSpecification.GetFileName? Resuelve el nombre a partir del nombre Unicode del archivo incrustado, el nombre de archivo regular o la clave de la colección, lo que esté disponible. Si ninguno está disponible, devuelve el nombre de reserva que usted pasa. El método se añadió en Aspose.PDF for .NET 26.9.

  2. ¿Esto funciona para PDFs que no son portfolios? Sí, para archivos adjuntos a nivel del documento. Los archivos adjuntos a una página específica mediante una anotación de adjunto de archivo (normalmente mostrada como un ícono de clip) se almacenan en la anotación, por lo que deben leerse desde FileAttachmentAnnotation.File, como se muestra en Extract files attached to pages.

  3. ¿Por qué envolver el resultado de GetFileName en Path.GetFileName? El nombre almacenado en un PDF lo establece quien creó el archivo y puede incluir segmentos de carpeta. Path.GetFileName conserva solo el nombre del archivo, de modo que cada adjunto se escribe dentro de su carpeta de salida.

  4. ¿Qué ocurre si dos archivos adjuntos tienen el mismo nombre?
    Con FileMode.Create, el segundo archivo sobrescribe al primero. Verifique si la ruta ya existe y añada un sufijo numérico, como se muestra en Handle duplicate and unsafe names.

  5. ¿Puedo procesar un adjunto sin guardarlo en disco? Sí. La propiedad Contents es un Stream, por lo que puedes copiarlo a un MemoryStream, subirlo o pasarlo a otra biblioteca en lugar de escribirlo en un FileStream.