Artigo Técnico

Intercâmbio de dados de formulário FDF, XFDF e XFA no Delphi

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

A razão de existirem tantos métodos é que os dados de formulário PDF não são uma coisa única. Um AcroForm preenchido tem valores de campos, mas também pode conter comentários de revisão, e um formulário XFA legado incorpora uma descrição de aplicativo XML inteira que não tem relação com nenhum dos dois. A losLab PDF Library mantém essas três preocupações em APIs separadas de propósito, porque mesclá-las forçaria um modelo de dados incorreto em pelo menos duas delas. Decidir qual família você precisa é a primeira escolha de design, e ela geralmente é resolvida no momento em que você sabe o que o sistema de recebimento do outro lado realmente consome

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

FDF e XFDF transportam as mesmas informações em duas sintaxes diferencas, e o XFA é um mundo à parte. FDF é um documento em miniatura com sintaxe PDF (ISO 32000-2 §12.7.8): um array /Fields de dicionários << /T (nome) /V (valor) >>, com strings escapadas exatamente como strings literais são escapadas dentro de um PDF. XFDF é a forma XML dos mesmos dados (ISO 19444), uma árvore <fields> que o Acrobat e a maioria dos back-ends de formulário leem e escrevem nativamente. O XFA não é nenhum dos dois: é um modelo XML Forms Architecture mais seus dados, armazenados como um XML Data Package (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 mantendo um documento que foi originalmente criado como um formulário XFA

Dentro de FDF e XFDF, a losLab PDF Library traça 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 grava a subárvore /Fields e não toca em mais nada. A família de anotações (ExportAnnotationsToFDF, ImportAnnotationsFromFDF, ExportAnnotationsToXFDF, ImportAnnotationsFromXFDF) lê e grava a subárvore /Annots, que é o que o Acrobat chama de Exportar Comentários. As duas nunca se sobrepõem, portanto, a exportação de valores de campos não coletará notas de revisão avulsas, e a importação de comentários não alterará os valores que um usuário já digitou. O que um widget faz quando é clicado ou recalculado é uma terceira preocupação, abordada na nota complementar sobre ações de formulário interativas 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 salve. O ImportFormDataFromXFDF e o ImportFormDataFromFDF analisam os dados de formulário recebidos, associam cada entrada a um campo AcroForm por seu nome totalmente qualificado, definem o valor e retornam a contagem de campos realmente atualizados. Ambos os métodos atualizam apenas campos que já existem no PDF de destino; nenhum cria um campo para um nome que o formulário não define, o que impede que um arquivo de dados corrompido ou hostil aumente silenciosamente o seu formulário

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;

Dois detalhes decidem se dados não triviais sobrevivem à viagem. O primeiro é o escape: o FDF armazena valores como strings literais de PDF, então um valor contendo parênteses, barra invertida ou byte não imprimível chega envolto no escape octal definido na ISO 32000-2 §7.3.4.2, e a losLab PDF Library inverte esse escape na importação para que os parênteses e as barras voltem como os caracteres literais que o usuário digitou. O segundo é a codificação: os métodos baseados em arquivo gravam 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) precisa para a viagem de ida e volta sem corrupção. Se você mesmo criar o XFDF, declare UTF-8 e grave em UTF-8, e a importação funcionará corretamente

Campos hierárquicos, seleções de múltiplos valores e rich text

Nomes de campos hierárquicos são o primeiro lugar 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 em um único atributo de nome; a ISO 19444 o aninha, 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 reúne os elementos aninhados de volta no título completo na importação, mantendo as duas direções simétricas. Na exportação, ela também ignora campos pai não terminais, porque um nó pai em um AcroForm carrega apenas o nível de nomenclatura e não possui valor próprio; emiti-lo produziria um <value></value> vazio que não corresponde ao formulário real. Caixas de listagem 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 só divide um campo em múltiplos valores quando ele é genuinamente uma escolha de seleção múltipla, de modo que um campo de texto comum com várias linhas mantém suas quebras de linha em vez de se fragmentar em valores falsos

Rich text é o terceiro, e o que as pessoas mais erram. Um campo com formatação armazena sua marcação na entrada RV como uma subárvore XHTML, e o XFDF a transporta em <value-richtext>. A losLab PDF Library grava essa subárvore como um fragmento XML ativo em vez de escapá-lo, para que uma ferramenta downstream leia rich text real em vez de uma string de tags visíveis; na importação, ela preserva a subárvore RV bruta e ainda aplica o <value> simples ao V. Onde um campo oferece ambos, o valor simples vence para V e o rich text o acompanha no RV, que é a regra de interoperabilidade que impede uma importação de rich text de reescrever silenciosamente um valor que outra ferramenta já definiu. Quando seu rich text existir para transmitir a estrutura do documento para tecnologia assistiva, 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 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;

Como fazer a 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, junto com suas contrapartes FDF, transportam marcações de estilo texto com os campos que realmente fazem a ida e volta de forma limpa: o subtipo de anotação, seu retângulo, seu índice de página baseado em 0, o autor em T, o assunto em Subj, o Contents simples e a cor, que mapeia a tripla DeviceRGB de /C do PDF para e a partir de um atributo #RRGGBB do XFDF. 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 ausente, de modo que um XFDF editado manualmente ou de terceiros não possa inserir uma anotação malformada no documento. O que o subconjunto ainda não transporta vale a pena ser dito claramente: conteúdo em rich-text (RC), popups, quadpoints, listas de tinta, vértices e fluxos de aparência convertidos em imagem estão fora do escopo por enquanto, de modo que a geometria de realce e aparências de carimbo personalizadas não sobreviverão a esse caminho. Para confirmar o que realmente foi aplicado, percorra as anotações da página ou a árvore de elementos mais ampla coberta 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;   // <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;

Gravando um pacote XFA XDP completo com SetXFAFromString

O XFA é a exceção, e o SetXFAFromString é como você grava tudo de uma vez. O método recebe um XML Data Package completo, o documento <xdp:xdp> com seus pacotes de modelo e conjuntos de dados, e o armazena como o fluxo referenciado de /AcroForm/XFA. A losLab PDF Library cria o contêiner AcroForm sob demanda quando o documento não tem nenhum, para que você não precise adicionar um campo descartável apenas para dar ao XFA onde se pendurar, e ela descarta qualquer estado XFA analisado que estava mantendo para que uma leitura posterior reflita o pacote que você acabou de gravar, em vez de um cache desatualizado. Como um pacote XFA é XML e não necessariamente UTF-8, a biblioteca detecta uma marca de ordem de byte UTF-16 (BOM) e decodifica o pacote corretamente antes de analisá-lo, o que é importante para pacotes produzidos por ferramentas que usam UTF-16 por padrão

Assim que o pacote estiver no lugar, o GetXFAFormFieldValue e o SetXFAFormFieldValue abordam campos individuais por meio de seu caminho Scripting Object Model (SOM). A losLab PDF Library aceita as raízes SOM padrão, bem como caminhos relativos puros, de modo que form1.FullName, $data.form1.FullName e xfa.datasets.data.form1.FullName todos se resolvem no 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 sempre emite o formulário totalmente qualificado

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;

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