💡 Ejemplo completo disponible en GitHub:
extract-annotations-from-pdf-using-groupdocs-parser-dotnet

Introduction

Un PDF que ha pasado por una revisión suele contener más que su texto visible: notas adhesivas, observaciones resaltadas y comentarios en línea dejados por los revisores. Desplazarse por cada página para encontrarlos no escala una vez que un documento ha pasado por varias rondas de retroalimentación. GroupDocs.Parser es una biblioteca .NET que lee las anotaciones incrustadas de un documento de forma programática, convirtiendo los comentarios dispersos de los revisores en datos estructurados que tu código puede procesar. Este tutorial muestra cómo extraer anotaciones de un PDF completo, desglosarlas página por página, obtenerlas junto con el texto del documento y exportar los resultados a CSV o JSON.

Me encontré con este problema mientras construía un rastreador de revisiones para un equipo de documentación: una nota de lanzamiento de 40 páginas había pasado por tres revisores, y abrir manualmente el archivo para encontrar cada comentario tomó más tiempo que corregir los problemas señalados. Extraer las anotaciones en unas pocas líneas de código convirtió eso en una tarea de dos minutos.

En las siguientes secciones aprenderás a:

  • Extraer cada anotación de un PDF en una sola pasada.
  • Etiquetar cada anotación con la página a la que pertenece.
  • Obtener el texto del documento y el texto de la anotación juntos en una única lectura.
  • Serializar los resultados a CSV o JSON para herramientas posteriores.

Why Extracting PDF Annotations Matters

Leer anotaciones de PDF de forma programática es útil para:

  • Flujos de revisión: recopila cada comentario del revisor sin abrir el archivo en un visor de PDF.
  • Colaboración: muestra secciones resaltadas o anotadas directamente dentro de tus propias herramientas.
  • Auditoría: conserva un registro de las marcas dejadas en un documento a lo largo del tiempo, incluso después de que se haya aplanado o finalizado.

GroupDocs.Parser añadió extracción nativa de anotaciones para documentos PDF en la versión 26.7 mediante el método GetAnnotations, junto con una nueva opción IncludeAnnotations en TextOptions para incorporar el texto de la anotación en una lectura de texto regular.

Prerequisites

  • .NET 6.0 o posterior
  • GroupDocs.Parser para .NET 26.7+ (licencia temporal)
  • Un archivo PDF con anotaciones existentes (p. ej., document-with-annotations.pdf)

Instala vía NuGet:

dotnet add package GroupDocs.Parser

How do I extract annotations from a PDF document?

Respuesta: Carga el archivo con Parser, luego llama a GetAnnotations() para todo el documento o a GetAnnotations(pageIndex) para una sola página. Cada resultado es una colección de objetos AnnotationItem cuyo atributo Value contiene el texto del comentario. Si prefieres ver los comentarios en línea con el contenido regular del documento, establece IncludeAnnotations en TextOptions y llama a GetText en su lugar.

Whole‑Document Extraction

El fragmento siguiente extrae todas las anotaciones del archivo en una única llamada, que es la forma más rápida de comprobar si un documento tiene comentarios abiertos.

// Extract every annotation from the whole document
var result = new List<string>();
using (var parser = new Parser(path))
{
    IEnumerable<AnnotationItem> annotations = parser.GetAnnotations();
    if (annotations == null)
    {
        return result; // format doesn't support annotations
    }

    foreach (var item in annotations)
    {
        result.Add(item.Value); // annotation text
    }
}
return result;

Puntos clave:

  • GetAnnotations() devuelve null cuando la extracción de anotaciones no está soportada para el documento, y una colección vacía cuando el documento simplemente no tiene ninguna.
  • Cada AnnotationItem expone su texto a través de la propiedad Value; ese es el único dato que el SDK reporta actualmente.
  • No se incluye atribución de página aquí; usa la sobrecarga por página que sigue a continuación si la necesitas.

Per‑Page Extraction

Cuando la ubicación de un comentario importa, recorre las páginas del documento y llama a GetAnnotations(pageIndex) para cada una.

// Tag each annotation with its zero-based page index
var result = new List<AnnotationRecord>();
using (var parser = new Parser(path))
{
    if (!parser.Features.Annotations)
    {
        return result;
    }

    var info = parser.GetDocumentInfo();
    if (info == null || info.PageCount == 0)
    {
        return result;
    }

    for (int pageIndex = 0; pageIndex < info.PageCount; pageIndex++)
    {
        IEnumerable<AnnotationItem> pageAnnotations = parser.GetAnnotations(pageIndex);
        if (pageAnnotations == null)
        {
            continue;
        }

        foreach (var item in pageAnnotations)
        {
            result.Add(new AnnotationRecord { PageIndex = pageIndex, Value = item.Value });
        }
    }
}
return result;

Puntos clave:

  • GetDocumentInfo().PageCount impulsa el bucle; no existe un “conteo de páginas de anotaciones” separado.
  • GetAnnotations(pageIndex) usa un índice basado en cero, igual que cualquier otro método a nivel de página de la API.
  • La lista resultante de AnnotationRecord tiene exactamente la forma que necesita una exportación a CSV o JSON.

Extracting Text Together with Annotations

En lugar de dos pasadas sobre el documento, puedes incorporar el texto de la anotación directamente en la salida de extracción de texto regular.

// Read document text with annotation text included
using (var parser = new Parser(path))
{
    var options = new TextOptions
    {
        IncludeAnnotations = true
    };

    using (TextReader reader = parser.GetText(options))
    {
        return reader?.ReadToEnd() ?? string.Empty;
    }
}

Puntos clave:

  • IncludeAnnotations es una propiedad de TextOptions, por lo que funciona con la misma llamada GetText que ya usas para extracción de texto plano.
  • Útil cuando deseas una salida única tipo transcripción en lugar de una lista de comentarios separada.
  • Combínalo con GetText(pageIndex, options) si solo lo necesitas para una página.

Checking Annotation Support First

No todos los formatos admiten anotaciones, por lo que vale la pena comprobarlo antes de construir lógica alrededor de GetAnnotations.

// Returns true if the loaded document format supports annotation extraction
using (var parser = new Parser(path))
{
    return parser.Features.Annotations;
}

Puntos clave:

  • Features.Annotations es una bandera booleana simple en la instancia de Parser.
  • Verificarla al inicio hace que la intención sea explícita, aunque GetAnnotations ya falla de forma elegante devolviendo null.

Exporting the Annotations to CSV

Una exportación a CSV permite a los revisores abrir la lista de comentarios directamente en Excel. El método a continuación escribe un archivo de dos columnas (page,value) a partir de los registros etiquetados por página creados antes.

var sb = new StringBuilder();
sb.AppendLine("page,value");

foreach (var record in records)
{
    sb.AppendLine($"{record.PageIndex},{CsvEscape(record.Value)}");
}

File.WriteAllText(outputPath, sb.ToString());

Puntos clave:

  • CsvEscape cita de forma segura los campos que contienen comas, comillas o saltos de línea.
  • El archivo resultante se abre directamente en Excel o puede canalizarse a una herramienta de tickets.

Helper: CsvEscape

if (string.IsNullOrEmpty(s)) return string.Empty;
if (s.Contains(",") || s.Contains("\"") || s.Contains("\n"))
{
    return "\"" + s.Replace("\"", "\"\"") + "\"";
}
return s;

Exporting the Annotations to JSON

Para canalizaciones que consumen comentarios de forma programática, un arreglo JSON suele ser más adecuado que un CSV plano.

var sb = new StringBuilder();
sb.AppendLine("[");

for (int i = 0; i < records.Count; i++)
{
    var comma = i < records.Count - 1 ? "," : string.Empty;
    sb.AppendLine($"  {{ \"page\": {records[i].PageIndex}, \"value\": \"{Escape(records[i].Value)}\" }}{comma}");
}

sb.AppendLine("]");
File.WriteAllText(outputPath, sb.ToString());

Puntos clave:

  • La salida es un arreglo plano de objetos { page, value }, fácil de deserializar por cualquier servicio posterior.
  • Escape mantiene la carga útil como JSON válido sin necesidad de una biblioteca de serialización.

Helper: Escape

return s?.Replace("\\", "\\\\").Replace("\"", "\\\"") ?? string.Empty;

Comparing Methods: When to Use Each

Método Mejor para Ventajas clave Limitaciones
Extracción de documento completo Ver rápidamente “¿existen comentarios?” Llamada única, código más sencillo No incluye atribución de página
Extracción por página Enrutar la retroalimentación a la sección correcta Resultados con etiqueta de página, listos para exportar Una llamada extra por página
Texto combinado + anotaciones Obtener una transcripción única No se necesita una segunda pasada sobre el documento Los comentarios no están separados del texto principal
Exportación a CSV Seguimiento basado en hojas de cálculo Fácil de abrir en Excel, legible por humanos Estructura plana limitada
Exportación a JSON Pipelines automatizados, sistemas de tickets Estructurado, legible por máquinas Carga ligeramente mayor

Comienza con la extracción de documento completo para confirmar que el archivo tiene comentarios que valga la pena procesar, y luego pasa a la extracción por página cuando necesites dirigir la retroalimentación a una sección específica.

Best Practices and Tips

  • Descarta Parser rápidamente: envuélvelo en un bloque using para liberar los recursos nativos.
  • Distingue null de vacío: GetAnnotations que devuelve null indica que el formato no es compatible; una colección vacía indica que el documento no tiene comentarios.
  • Comprueba Features.Annotations en trabajos por lotes: omite los archivos no compatibles temprano en lugar de depender de una verificación null profunda dentro del bucle.
  • Reutiliza la lista etiquetada por página: constrúyela una vez con ExtractAnnotationsByPage y alimenta tanto al exportador CSV como al JSON desde los mismos datos, de modo que ambas salidas nunca diverjan.
  • Seguridad: el texto de la anotación es entrada libre del revisor; trátalo como cualquier otra cadena no confiable antes de renderizarlo en una UI o informe.

Conclusion

GroupDocs.Parser te brinda una forma directa y programática de extraer los comentarios de los revisores de un PDF, en lugar de buscarlos manualmente. Al extraer anotaciones para todo el documento, etiquetarlas por página o incorporarlas al flujo de texto regular, puedes crear flujos de revisión que expongan la retroalimentación en el momento en que el documento llega a tu canalización. Exporta los resultados a CSV o JSON y conéctalos directamente a las herramientas que tu equipo ya utiliza.

Próximos pasos:

Additional Resources