Los archivos PDF interactivos se dividen en dos bandos. AcroForms son los campos clásicos de PDF que residen sobre el contenido fijo de la página. Los formularios XFA corresponden a la arquitectura basada en XML que Adobe introdujo en la versión PDF 1.5, donde el propio documento sirve como contenedor para un conjunto de datos XML y un motor de diseño. Extraer datos de un formulario XFA implica consultar ese árbol XML y, si bien la mayoría de los valores son cadenas simples, los campos de texto enriquecido no lo son. Un campo de texto enriquecido almacena su formato como XHTML incrustado dentro de un elemento contenedor denominado exData. Para extraer el texto hay que desenvolverlo, y para extraer los hipervínculos hay que analizarlo
La estructura del texto enriquecido de XFA
Un campo de texto XFA ordinario almacena su valor directamente. Usted solicita al TXMLNode su contenido de texto y recibe exactamente lo que el usuario escribió. Un campo de texto enriquecido almacena su valor dentro de un nodo exData, que lleva un atributo contentType configurado como text/html. El hijo de ese nodo es una etiqueta body en el espacio de nombres http://www.w3.org/1999/xhtml, y dentro de ese cuerpo se encuentra el marcado
Esto significa que no puede simplemente leer el texto del nodo. Si le solicita a un analizador XML el contenido de texto del nodo exData, obtendrá el texto concatenado de todos sus hijos, despojado de sus etiquetas. Tanto un salto de párrafo <p> como un hipervínculo <a> desaparecen, dejando el texto amontonado y los destinos de los enlaces perdidos. Para recuperar el formato debe serializar de nuevo el nodo body a una cadena XML, manteniendo intactos a sus hijos
<!-- How a rich text field looks inside the XFA dataset -->
<Comments>
<exData contentType="text/html">
<body xmlns="http://www.w3.org/1999/xhtml">
<p style="text-decoration:none">See the <a href="https://example.com">guide</a>.</p>
</body>
</exData>
</Comments>
Desenvolviendo los exData en HotPDF
HotPDF carga el conjunto de datos XFA en una interfaz estándar IXMLDocument de Delphi cuando se lo solicita. El conjunto de datos es la porción de la arquitectura XFA que alberga las respuestas del usuario, separada de la plantilla y de las reglas de diseño. Puesto que se trata de XML estándar, el texto enriquecido se extrae buscando el nodo exData y serializando su primer hijo
uses
XMLIntf, HPDFDoc;
function GetRichTextXHTML(const FieldNode: IXMLNode): string;
var
ExDataNode, BodyNode: IXMLNode;
begin
Result := '';
if not Assigned(FieldNode) then
Exit;
// Find the exData wrapper
ExDataNode := FieldNode.ChildNodes.FindNode('exData');
if not Assigned(ExDataNode) then
Exit; // Not a rich text field
// The body tag is the first child
BodyNode := ExDataNode.ChildNodes.First;
if Assigned(BodyNode) then
Result := BodyNode.XML; // Serialize back to string with tags
end;
La propiedad XML del nodo body devuelve la cadena XHTML completa, comenzando por <body xmlns="http://www.w3.org/1999/xhtml"> y terminando con </body>. Se trata del marcado exacto que Acrobat escribió cuando el usuario guardó el archivo, listo para ser entregado a un control de navegador, ingresado en un generador de informes o analizado en busca de enlaces
Extracción de los hipervínculos
La razón más frecuente para indagar en el texto enriquecido de XFA es la búsqueda de hipervínculos. Un usuario introdujo una URL, Acrobat la enlazó automáticamente, y el backend necesita saber a dónde apunta. Dado que el body es XML válido, no una sopa de etiquetas HTML, los enlaces se pueden extraer recorriendo el DOM. No hay necesidad de utilizar expresiones regulares que fallan con las etiquetas anidadas
Un recorrido recursivo examina cada nodo. Si el nodo es un elemento y su nombre es a (o a con un prefijo de espacio de nombres), lee el atributo href para conocer el destino, y el contenido de texto para saber cuál es el texto clicable. Si se trata de cualquier otro elemento, repite la operación de forma recursiva con sus hijos
type
THyperlinkInfo = record
URL: string;
Text: string;
end;
THyperlinkArray = TArray<THyperlinkInfo>;
procedure ExtractLinks(const Node: IXMLNode; var Links: THyperlinkArray);
var
I: Integer;
Child: IXMLNode;
LinkInfo: THyperlinkInfo;
begin
if not Assigned(Node) then
Exit;
// Is this an anchor tag? LocalName ignores namespace prefixes like xhtml:
if (Node.NodeType = ntElement) and SameText(Node.LocalName, 'a') then
begin
if Node.HasAttribute('href') then
begin
LinkInfo.URL := Node.Attributes['href'];
LinkInfo.Text := Node.Text; // The clickable text inside the tag
SetLength(Links, Length(Links) + 1);
Links[High(Links)] := LinkInfo;
end;
end;
// Recurse into children (paragraphs, spans, etc.)
for I := 0 to Node.ChildNodes.Count - 1 do
begin
Child := Node.ChildNodes[I];
if Child.NodeType = ntElement then
ExtractLinks(Child, Links);
end;
end;
Manejo del prefijo del espacio de nombres
Los datos XFA no siempre declaran el espacio de nombres XHTML en la propia etiqueta body. A veces, el conjunto de datos declara un prefijo en el nivel superior, como xmlns:xhtml="http://www.w3.org/1999/xhtml", y escribe <xhtml:body> y <xhtml:a>. La propiedad LocalName de IXMLNode maneja esto de forma impecable. Devuelve a ya sea que la etiqueta se haya escrito como <a> o <xhtml:a>, de forma que la lógica de extracción jamás tiene que adivinar qué prefijo escogió el generador del PDF
El único caso extremo es la codificación del texto. El conjunto de datos XFA siempre está en formato UTF-8 dentro del PDF, pero el analizador XML de Delphi se encarga de la traducción a WideString o UnicodeString por usted. Para cuando lee el atributo href, cualquier entidad HTML como & ya ha sido resuelta, y todos los caracteres no latinos son caracteres nativos de Delphi. Usted puede pasar la URL directamente a una base de datos o a una petición web sin necesidad de decodificarla primero
Leer conjuntos de datos XFA conforma la mitad de la historia de los formularios. La otra mitad corresponde a los clásicos AcroForms, que guardan sus datos en diccionarios PDF en vez de XML. Si requiere leer campos de ambos tipos de documentos, la guía sobre cómo extraer campos de formulario PDF muestra el funcionamiento del enfoque AcroForm. Si usted está acoplando (flattening) formularios para hacerlos permanentes, cómo HotPDF acopla anotaciones y campos explica la diferencia entre ambas arquitecturas en el momento del renderizado. Ambos tipos de formularios, y las APIs de extracción para leerlos, vienen incluidos en el Componente HotPDF para Delphi y C++Builder