Artigo Técnico

Intercâmbio de Dados de Formulários FDF, XFDF e XFA no Delphi

A losLab PDF Library move dados de formulários para dentro e para fora de um PDF de três formas diferentes: FDF e XFDF para valores de campos AcroForm, os mesmos dois formatos para comentários de anotação, e um pacote XFA XDP completo gravado diretamente no formulário. O ExportFormDataToXFDF, ImportFormDataFromXFDF, ImportAnnotationsFromFDF, ExportAnnotationsToXFDF e SetXFAFromString são os pontos de entrada, e cada um deles possui uma variante de ficheiro e uma variante de string

A razão pela qual existem tantos métodos é que os dados de formulários PDF não são uma única coisa. Um AcroForm preenchido possui valores de campos, pode também conter comentários de revisão, e um formulário XFA legado incorpora uma descrição de aplicação XML completa que nada tem a ver com nenhum dos dois. A losLab PDF Library mantém estas três preocupações em APIs separadas de propósito, porque fundi-las forçaria o modelo de dados errado em pelo menos duas delas. Decidir qual a família de que necessita é a primeira escolha de design, e geralmente fica decidida no momento em que sabe o que o sistema recetor do outro lado realmente consome

Qual é a diferença entre dados de formulários FDF, XFDF e XFA?

O FDF e o XFDF transportam a mesma informação em duas sintaxes diferenciativas, e o XFA é um mundo à parte. O FDF é um documento de sintaxe PDF em miniatura (ISO 32000-2 §12.7.8): uma matriz /Fields de dicionários << /T (nome) /V (valor) >>, com strings escapadas exatamente da forma como as strings literais são escapadas dentro de um PDF. O XFDF é a forma XML dos mesmos dados (ISO 19444), uma árvore <fields> que o Acrobat e a maioria dos back-ends de formulários leem e escrevem nativamente. O XFA não é nenhum dos dois: é um modelo de XML Forms Architecture mais os seus dados, armazenados como um Pacote de Dados XML (XDP) que o PDF referencia a partir de /AcroForm/XFA. Escolha FDF ou XFDF quando interoperar em valores de campos, e recorra ao XFA apenas quando estiver a manter um documento que foi originalmente criado como um formulário XFA

Dentro do FDF e XFDF, a losLab PDF Library desenha uma segunda linha: valores de campos versus comentários de anotação. A família de dados de formulário (ExportFormDataToFDF, ImportFormDataFromFDF, ExportFormDataToXFDF, ImportFormDataFromXFDF) lê e escreve a subárvore /Fields e não toca em mais nada. A família de anotações (ExportAnnotationsToFDF, ImportAnnotationsFromFDF, ExportAnnotationsToXFDF, ImportAnnotationsFromXFDF) lê e escreve a subárvore /Annots, que é o que o Acrobat entende por Exportar Comentários. As duas nunca se sobrepõem, de modo que a exportação de valores de campos não recolherá notas de revisão perdidas, e a importação de comentários não perturbará os valores que um utilizador já digitou. O que um widget faz quando é clicado ou recalculado é uma terceira preocupação, abordada na nota complementar sobre ações interativas de formulários e JavaScript

Como preencher um formulário PDF a partir de FDF ou XFDF no Delphi?

Carregue o documento, chame um método de importação e guarde. O ImportFormDataFromXFDF e o ImportFormDataFromFDF analisam os dados do formulário de entrada, associam cada entrada a um campo AcroForm pelo seu nome totalmente qualificado, definem o valor e devolvem a contagem de campos realmente atualizados. Ambos os métodos apenas atualizam campos que já existem no PDF de destino; nenhum cria um campo para um nome que o formulário não defina, o que impede que um ficheiro de dados perdido ou hostil aumente silenciosamente o seu formulário

var
  Lib: TPDFlib;
  FieldsSet: Integer;
begin
  Lib := TPDFlib.Create;
  try
    Lib.LoadFromFile('application-blank.pdf', '');
    // XFDF produzido por um sistema de gestão de casos, já UTF-8 no disco
    FieldsSet := Lib.ImportFormDataFromXFDF('applicant-1042.xfdf');
    if FieldsSet > 0 then
      Lib.SaveToFile('application-filled.pdf');
  finally
    Lib.Free;
  end;
end;

Dois detalhes determinam se os dados não triviais sobrevivem à viagem. O primeiro é o escape (escaping): o FDF armazena valores como strings literais de PDF, pelo que um valor contendo um parêntese, uma barra invertida ou um byte não imprimível chega envolto no escape octal definido na norma ISO 32000-2 §7.3.4.2, e a losLab PDF Library inverte esse escape na importação para que os parênteses e barras regressem como os caracteres literais que o utilizador digitou. O segundo é a codificação: os métodos baseados em ficheiros escrevem e leem XFDF e FDF como UTF-8, que é o que a declaração XFDF promete e o que qualquer valor de campo não ASCII (um nome acentuado, um símbolo de moeda, um endereço CJK) necessita para completar a viagem sem corrupção. Se construir o XFDF por si mesmo, declare UTF-8 e escreva UTF-8, e a importação concordará consigo

Campos hierárquicos, escolhas multi-valor e rich text

Os nomes de campos hierárquicos são o primeiro local onde um exportador ingénuo falha. O AcroForm aborda um campo aninhado com um título completo com pontos, como Applicant.FullName, mas o XFDF não coloca essa string com pontos num único atributo de nome; a norma ISO 19444 aninha-o, como <field name="Applicant"><field name="FullName">. A losLab PDF Library divide o título com pontos em elementos <field> aninhados na exportação e reagrupa os elementos aninhados de volta no título completo na importação, para que as duas direções permaneçam simétricas. Na exportação, também ignora campos pais não terminais, porque um nó pai num AcroForm carrega apenas o nível de nomenclatura e não tem valor próprio; emiti-lo produziria um <value></value> vazio que não corresponde ao formulário real. As caixas de lista de seleção múltipla são a segunda armadilha: um campo de escolha pode conter vários valores selecionados ao mesmo tempo, o que o XFDF expressa como elementos <value> repetidos e o FDF expressa como uma matriz /V, e a losLab PDF Library apenas divide um campo em múltiplos valores quando se trata de uma escolha de seleção múltipla genuína, para que um campo de texto multilinha comum mantenha as suas quebras de linha em vez de se fragmentar em valores falsos

O rich text é o terceiro, e aquele em que as pessoas erram com mais frequência. Um campo com formatação armazena a sua marcação na entrada RV como uma subárvore XHTML, e o XFDF transporta-a em <value-richtext>. A losLab PDF Library escreve essa subárvore como um fragmento XML vivo em vez de a escapar, para que uma ferramenta a jusante leia rich text real em vez de uma string de tags visíveis; na importação, preserva a subárvore RV em bruto e continua a aplicar o <value> simples a V. Quando um campo oferece ambos, o valor simples ganha para V e o rich text acompanha em RV, que é a regra de interoperabilidade que impede que uma importação de rich text reescreva silenciosamente um valor que outra ferramenta já definiu. Quando o seu rich text existe para transmitir a estrutura do documento para tecnologia de assistência, trate-o da mesma forma que trataria a ordem de leitura discutida na nota sobre PDF etiquetado e estrutura de acessibilidade

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 reagrupa-se; Skills preenche as matrizes /V + /I;
    // Notes obtém V de <value> e RV de <value-richtext>
    Lib.ImportFormDataFromXFDFString(XFDF);
    Lib.SaveToFile('intake-filled.pdf');
  finally
    Lib.Free;
  end;
end;

Como fazer a viagem de ida e volta de comentários de anotação como FDF ou XFDF?

Os comentários viajam na família de anotações, e o subconjunto suportado é deliberadamente pequeno. O ExportAnnotationsToXFDF e o ImportAnnotationsFromXFDF, com os seus homólogos FDF, transportam a marcação de estilo de texto com os campos que realmente completam a viagem de forma limpa: o subtipo da anotação, o seu retângulo, o seu índice de página baseado em 0, o autor em T, o assunto em Subj, o Contents simples, e a cor, que mapeia o triplo DeviceRGB /C do PDF para e a partir de um atributo XFDF #RRGGBB. A importação aceita apenas nomes de elementos conhecidos e ignora qualquer entrada cuja página esteja fora do intervalo ou cujo retângulo esteja em falta, para que um XFDF editado manualmente ou estrangeiro não possa colocar uma anotação malformada no documento. O que o subconjunto ainda não transporta vale a pena ser dito claramente: conteúdos rich text (RC), popups, quadpoints, listas de tinta (ink lists), vértices e fluxos de aparência incorporados estão fora de âmbito por agora, pelo que a geometria de realce e aparências de carimbos personalizados não sobreviverão a este caminho. Para confirmar o que realmente foi importado, percorra as anotações da página ou a árvore de elementos mais ampla abordada em pesquisa de texto e enumeração 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;   // apenas subconjunto <annots>
  
    Dest.LoadFromFile('clean-copy.pdf', '');     // as páginas devem alinhar-se por índice
    Dest.ImportAnnotationsFromXFDFString(Xfdf);
    Dest.SaveToFile('clean-copy-commented.pdf');
  finally
    Dest.Free;
    Src.Free;
  end;
end;

Escrever um pacote XFA XDP completo com SetXFAFromString

O XFA é o caso atípico, e o SetXFAFromString é a forma de escrever a totalidade do pacote de uma só vez. O método recebe um Pacote de Dados XML completo, o documento <xdp:xdp> com os seus pacotes de modelo e datasets, e armazena-o como o fluxo (stream) referenciado a partir de /AcroForm/XFA. A losLab PDF Library cria o contentor AcroForm sob procura quando o documento não possui nenhum, pelo que não tem de adicionar um campo descartável apenas para dar ao XFA um local onde se fixar, e descarta qualquer estado XFA analisado que estivesse a guardar para que uma leitura posterior reflita o pacote que acabou de escrever em vez de uma cache obsoleta. Como um pacote XFA é XML e não necessariamente UTF-8, a biblioteca deteta uma marca de ordem de bytes (BOM) UTF-16 e descodifica o pacote corretamente antes de o analisar, o que é importante para pacotes produzidos por ferramentas que têm o UTF-16 como padrão

Assim que o pacote estiver no lugar, o GetXFAFormFieldValue e o SetXFAFormFieldValue abordam campos individuais através do seu caminho Scripting Object Model (SOM). A losLab PDF Library aceita as raízes SOM padrão, bem como caminhos relativos simples, pelo que form1.FullName, $data.form1.FullName e xfa.datasets.data.form1.FullName resolvem todos para o mesmo nó de dados, e o lado do modelo aceita $template e xfa.template da mesma forma. Essa tolerância é importante quando os caminhos SOM são gerados por outro sistema que emite sempre a forma totalmente qualificada

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);      // cria o contentor AcroForm se não existir nenhum
    // lê de volta através do DOM de dados com um caminho SOM
    if Lib.GetXFAFormFieldValue('form1.FullName') = 'Alice Example' then
      Lib.SetXFAFormFieldValue('form1.FullName', 'Bob Example');
    Lib.SaveToFile('xfa-packet.pdf');
  finally
    Lib.Free;
  end;
end;

O resumo honesto é que os valores dos campos completam totalmente a viagem de ida e volta através de FDF e XFDF, incluindo hierarquia, seleção múltipla e rich text; os comentários de anotação completam a viagem como um subconjunto de marcação de texto definido com lacunas claras; e o XFA é escrito e lido como um pacote completo com acesso SOM por campo no topo. Faça corresponder o formato ao consumidor, declare UTF-8 para qualquer coisa que construa manualmente, e lembre-se de que os métodos de importação apenas tocam em campos e anotações que já se ajustem ao documento. Os métodos de dados de formulário, anotação e XFA aqui descritos fazem parte da losLab PDF Library para Delphi e C++Builder, cuja referência contém a lista completa de parâmetros para cada ponto de entrada de exportação e importação