Artículo técnico

Intercambio de datos de formularios FDF, XFDF y XFA en Delphi

losLab PDF Library mueve datos de formularios dentro y fuera de un PDF de tres maneras diferentes: FDF y XFDF para valores de campos AcroForm, esos mismos dos formatos para comentarios de anotación, y un paquete XFA XDP completo escrito directamente en el formulario. ExportFormDataToXFDF, ImportFormDataFromXFDF, ImportAnnotationsFromFDF, ExportAnnotationsToXFDF y SetXFAFromString son los puntos de entrada, y cada uno de ellos tiene una variante de archivo y una variante de cadena

La razón por la que hay tantos métodos es que los datos de un formulario PDF no son una sola cosa. Un AcroForm completo tiene valores de campo, también puede contener comentarios de revisión, y un formulario XFA heredado incrusta una descripción completa de la aplicación XML que no tiene nada que ver con ninguno de los dos. losLab PDF Library mantiene estas tres preocupaciones en API separadas a propósito, porque fusionarlas forzaría el modelo de datos incorrecto en al menos dos de ellas. Decidir qué familia necesita es la primera elección de diseño, y generalmente se resuelve en el momento en que sabe qué consume realmente el sistema receptor del otro lado

¿Cuál es la diferencia entre los datos de formulario FDF, XFDF y XFA?

FDF y XFDF contienen la misma información en dos sintaxis diferentes, y XFA es un mundo aparte. FDF es un documento de sintaxis PDF en miniatura (ISO 32000-2 §12.7.8): un arreglo /Fields de diccionarios << /T (nombre) /V (valor) >>, con cadenas de texto escapadas exactamente de la misma manera que se escapan las cadenas literales dentro de un PDF. XFDF es la forma XML de los mismos datos (ISO 19444), un árbol <fields> que Acrobat y la mayoría de los back-ends de formularios leen y escriben de forma nativa. XFA no es ninguna de las dos cosas: es una plantilla de XML Forms Architecture más sus datos, almacenados como un paquete de datos XML (XDP) al que el PDF hace referencia desde /AcroForm/XFA. Elija FDF o XFDF cuando interactúe con valores de campo, y recurra a XFA solo cuando esté manteniendo un documento que fue creado originalmente como un formulario XFA

Dentro de FDF y XFDF, losLab PDF Library dibuja una segunda línea: valores de campos versus comentarios de anotaciones. La familia de datos de formulario (ExportFormDataToFDF, ImportFormDataFromFDF, ExportFormDataToXFDF, ImportFormDataFromXFDF) lee y escribe el subárbol /Fields y no toca nada más. La familia de anotaciones (ExportAnnotationsToFDF, ImportAnnotationsFromFDF, ExportAnnotationsToXFDF, ImportAnnotationsFromXFDF) lee y escribe el subárbol /Annots en su lugar, que es lo que Acrobat define como Exportar comentarios. Ambos nunca se superponen, por lo que exportar valores de campos no arrastrará notas de revisión perdidas, e importar comentarios no alterará los valores que el usuario ya haya ingresado. Lo que hace un componente visual (widget) al hacer clic o recalcularlo es una tercera preocupación, cubierta en la nota complementaria sobre acciones de formulario interactivas y JavaScript

¿Cómo rellenar un formulario PDF desde FDF o XFDF en Delphi?

Cargue el documento, llame a un método de importación y guarde. ImportFormDataFromXFDF e ImportFormDataFromFDF analizan cada uno los datos de formulario entrantes, asocian cada entrada con un campo AcroForm por su nombre completamente calificado, establecen el valor y devuelven el recuento de campos actualizados realmente. Ambos métodos solo actualizan campos que ya existen en el PDF de destino; ninguno inventa un campo para un nombre que el formulario no define, lo que evita que un archivo de datos perdido o malicioso amplíe silenciosamente su formulario

var
  Lib: TPDFlib;
  FieldsSet: Integer;
begin
  Lib := TPDFlib.Create;
  try
    Lib.LoadFromFile('application-blank.pdf', '');
    // XFDF produced by a case-management system, already UTF-8 on disk
    FieldsSet := Lib.ImportFormDataFromXFDF('applicant-1042.xfdf');
    if FieldsSet > 0 then
      Lib.SaveToFile('application-filled.pdf');
  finally
    Lib.Free;
  end;
end;

Dos detalles deciden si los datos no triviales sobreviven al viaje. El primero es el escapado: FDF almacena valores como cadenas literales de PDF, por lo que un valor que contiene un paréntesis, una barra invertida o un byte no imprimible llega envuelto en el escapado octal definido en ISO 32000-2 §7.3.4.2, y losLab PDF Library invierte ese escapado al importar para que los paréntesis y las barras vuelvan como los caracteres literales que el usuario escribió. El segundo es la codificación: los métodos basados en archivos escriben y leen XFDF y FDF como UTF-8, que es lo que promete la declaración XFDF y lo que cualquier valor de campo no ASCII (un nombre con acento, un símbolo de moneda, una dirección CJK) necesita para realizar un viaje de ida y vuelta sin corrupción. Si construye el XFDF usted mismo, declare UTF-8 y escriba UTF-8, y la importación coincidirá con usted

Campos jerárquicos, selecciones de múltiples valores y texto enriquecido

Los nombres de campos jerárquicos son el primer lugar donde falla un exportador ingenuo. AcroForm se dirige a un campo anidado con un título completo separado por puntos, como Applicant.FullName, pero XFDF no coloca esa cadena separada por puntos en un solo atributo de nombre; ISO 19444 lo anida, como <field name="Applicant"><field name="FullName">. losLab PDF Library divide el título separado por puntos en elementos <field> anidados al exportar y vuelve a ensamblar los elementos anidados en el título completo al importar, de modo que ambas direcciones sigan siendo simétricas. Al exportar, también omite los campos principales no terminales, porque un nodo principal en un AcroForm solo lleva el nivel de nomenclatura y no tiene valor propio; emitirlo produciría un <value></value> vacío que no coincide con el formulario real. Las casillas de lista de selección múltiple son la segunda trampa: un campo de elección puede contener varios valores seleccionados a la vez, lo que XFDF expresa como elementos <value> repetidos y FDF expresa como una matriz /V, y losLab PDF Library solo divide un campo en múltiples valores cuando se trata verdaderamente de una opción de selección múltiple, por lo que un campo de texto de varias líneas ordinario conserva sus saltos de línea en lugar de dividirse en valores falsos

El texto enriquecido es el tercero, y el que la gente suele hacer mal con más frecuencia. Un campo con formato almacena su marcado en la entrada RV como un subárbol XHTML, y XFDF lo transporta en <value-richtext>. losLab PDF Library escribe ese subárbol como un fragmento XML activo en lugar de escaparlo, por lo que una herramienta posterior lee texto enriquecido real en lugar de una cadena de etiquetas visibles; al importar, conserva el subárbol RV sin procesar y sigue aplicando el <value> simple a V. Cuando un campo ofrece ambos, el valor simple gana para V y el texto enriquecido se incluye en RV, que es la regla de interoperabilidad que evita que una importación de texto enriquecido reescriba silenciosamente un valor que otra herramienta ya estableció. Cuando su texto enriquecido exista para transmitir la estructura del documento a la tecnología de asistencia, trátelo de la misma manera que trataría el orden de lectura analizado en la nota sobre PDF etiquetado y estructura de accesibilidad

var
  Lib: TPDFlib;
const
  XFDF =
    '<?xml version="1.0" encoding="UTF-8"?>' +
    '<xfdf xmlns="http://ns.adobe.com/xfdf/" xml:space="preserve">' +
    '<fields>' +
    '  <field name="Applicant">' +
    '    <field name="FullName"><value>Alice Example</value></field>' +
    '  </field>' +
    '  <field name="Skills">' +
    '    <value>Delphi</value><value>PDF</value>' +
    '  </field>' +
    '  <field name="Notes">' +
    '    <value>See attachment</value>' +
    '    <value-richtext><body><p>See <b>attachment</b></p></body></value-richtext>' +
    '  </field>' +
    '</fields></xfdf>';
begin
  Lib := TPDFlib.Create;
  try
    Lib.LoadFromFile('intake.pdf', '');
    // Applicant.FullName reassembles; Skills fills the /V + /I arrays;
    // Notes gets V from <value> and RV from <value-richtext>
    Lib.ImportFormDataFromXFDFString(XFDF);
    Lib.SaveToFile('intake-filled.pdf');
  finally
    Lib.Free;
  end;
end;

¿Cómo realizar el viaje de ida y vuelta de comentarios de anotaciones como FDF o XFDF?

Los comentarios viajan en la familia de anotaciones, y el subconjunto compatible es deliberadamente pequeño. ExportAnnotationsToXFDF e ImportAnnotationsFromXFDF, junto con sus homólogos de FDF, transportan el marcado de estilo de texto con los campos que realmente se completan de forma limpia: el subtipo de anotación, su rectángulo, su índice de página basado en 0, el autor en T, el asunto en Subj, el contenido Contents simple y el color, que mapea el triple DeviceRGB /C de PDF hacia y desde un atributo XFDF #RRGGBB. La importación acepta solo nombres de elementos conocidos y omite cualquier entrada cuya página esté fuera de rango o cuyo rectángulo falte, de modo que un XFDF editado a mano o externo no pueda colocar una anotación malformada en el documento. Lo que el subconjunto aún no transporta vale la pena expresarlo claramente: el contenido de texto enriquecido (RC), las ventanas emergentes (popups), los puntos cuádruples (quadpoints), las listas de tinta, los vértices y los flujos de apariencia incrustados están fuera de alcance por ahora, por lo que la geometría de resaltado y las apariencias de sellos personalizados no sobrevivirán a esta ruta. Para confirmar lo que realmente se importó, recorra las anotaciones de la página o el árbol de elementos más amplio cubierto en búsqueda de texto y enumeración de elementos de página

var
  Src, Dest: TPDFlib;
  Xfdf: WideString;
begin
  Src := TPDFlib.Create;
  Dest := TPDFlib.Create;
  try
    Src.LoadFromFile('reviewed.pdf', '');
    Xfdf := Src.ExportAnnotationsToXFDFString;   // <annots> subset only
 
    Dest.LoadFromFile('clean-copy.pdf', '');     // pages must line up by index
    Dest.ImportAnnotationsFromXFDFString(Xfdf);
    Dest.SaveToFile('clean-copy-commented.pdf');
  finally
    Dest.Free;
    Src.Free;
  end;
end;

Escribir un paquete XFA XDP completo con SetXFAFromString

XFA es el caso atípico, y SetXFAFromString es cómo se escribe todo a la vez. El método toma un paquete de datos XML completo, el documento <xdp:xdp> con sus paquetes de plantilla y conjuntos de datos, y lo almacena como el flujo de datos (stream) al que hace referencia /AcroForm/XFA. losLab PDF Library crea el contenedor AcroForm a pedido cuando el documento no tiene ninguno, por lo que no tiene que agregar un campo desechable solo para darle a XFA un lugar donde colgar, y descarta cualquier estado XFA analizado que estuviera reteniendo para que una lectura posterior refleje el paquete que acaba de escribir en lugar de una caché obsoleta. Debido a que un paquete XFA es XML y no necesariamente UTF-8, la biblioteca detecta una marca de orden de bytes UTF-16 y decodifica el paquete correctamente antes de analizarlo, lo cual es importante para los paquetes producidos por herramientas que usan UTF-16 de manera predeterminada

Una vez que el paquete está en su lugar, GetXFAFormFieldValue and SetXFAFormFieldValue direccionan campos individuales a través de su ruta Scripting Object Model (SOM). losLab PDF Library acepta las raíces SOM estándar así como rutas relativas simples, por lo que form1.FullName, $data.form1.FullName y xfa.datasets.data.form1.FullName se resuelven en el mismo nodo de datos, y el lado de la plantilla acepta $template y xfa.template de la misma manera. Esa tolerancia es importante cuando las rutas SOM son generadas por otro sistema que siempre emite el formulario completamente calificado

var
  Lib: TPDFlib;
const
  Xdp =
    '<?xml version="1.0" encoding="UTF-8"?>' +
    '<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">' +
    '  <template xmlns="http://www.xfa.org/schema/xfa-template/3.3/">' +
    '    <subform name="form1">' +
    '      <field name="FullName"><ui><textEdit/></ui></field>' +
    '    </subform>' +
    '  </template>' +
    '  <xfa:datasets xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/">' +
    '    <xfa:data><form1><FullName>Alice Example</FullName></form1></xfa:data>' +
    '  </xfa:datasets>' +
    '</xdp:xdp>';
begin
  Lib := TPDFlib.Create;
  try
    Lib.SetXFAFromString(Xdp, 0);      // creates the AcroForm container if none exists
    // read back through the data DOM with an SOM path
    if Lib.GetXFAFormFieldValue('form1.FullName') = 'Alice Example' then
      Lib.SetXFAFormFieldValue('form1.FullName', 'Bob Example');
    Lib.SaveToFile('xfa-packet.pdf');
  finally
    Lib.Free;
  end;
end;

El resumen honesto es que los valores de campo realizan un viaje de ida y vuelta completo a través de FDF y XFDF, incluyendo jerarquía, selección múltiple y texto enriquecido; los comentarios de anotaciones viajan en un subconjunto de marcado de texto definido con brechas claras; y XFA se escribe y lee como un paquete completo con acceso SOM por campo además. Haga coincidir el formato con el consumidor, declare UTF-8 para cualquier cosa que cree a mano y recuerde que los métodos de importación solo tocan campos y anotaciones que ya se ajustan al documento. Los métodos de datos de formularios, anotaciones y XFA descritos aquí forman parte de losLab PDF Library para Delphi y C++Builder, cuya referencia contiene la lista completa de parámetros para cada punto de entrada de exportación e importación