A losLab PDF Library lê e grava nós de dados repetidos em formulários XFA dinâmicos por meio de expressões de caminho XFA 3.3 SOM: GetXFAFormFieldValue e SetXFAFormFieldValue aceitam as raízes $data, $record e !data, curingas de ocorrência [*], seletores descendentes e curingas de filhos, a propriedade parent, seletores de classe #dataGroup/#dataValue e predicados simples no estilo FormCalc, de modo que um programa Delphi possa atualizar cada linha de fatura em um formulário fiscal governamental com uma única chamada. Este artigo é a referência de endereçamento; para mover conjuntos inteiros de dados entre arquivos, consulte o artigo complementar sobre intercâmbio de dados de formulário FDF, XFDF e XFA
O problema aparece no momento em que um formulário deixa de ser plano. Uma declaração de imposto ou de alfândega criada como um formulário XFA dinâmico não tem doze campos chamados de Total_Price_1 a Total_Price_12; ela tem um subformulário Detail declarado uma vez no DOM de modelo (Template DOM) e instanciado tantas vezes quantas os dados exigirem. Os valores residem em uma segunda árvore, o DOM de Dados (Data DOM) dentro do pacote de datasets, onde cada item de linha é um elemento <Detail> repetido sob <Receipt>. Nomes de campos planos não conseguem endereçar "o preço na terceira linha" ou "cada linha acima de 200"; esse é exatamente o trabalho que o capítulo Scripting Object Model da especificação Adobe XFA 3.3 confere às expressões SOM, e é a sintaxe com a qual as APIs de valor de campo XFA da losLab PDF Library se comunicam
Como endereçar nós de dados XFA no Delphi?
Cada caminho de datasets começa a partir de uma raiz, e a losLab PDF Library aceita as grafias padrão de forma intercambiável: $data é a forma curta da norma XFA 3.3 para xfa.datasets.data, !data é a forma curta originada em xfa.datasets, e em pacotes que não usam processamento de registros, $record é resolvido para o registro de dados externo, o primeiro elemento sob o nó de dados. APIs do lado do modelo, como SetXFAFormFieldAccess, aceitam $template and xfa.template da mesma maneira. Os segmentos podem ser delimitados por pontos ou por barras ($data.Receipt.Tax ou $data/Receipt/Tax), o que é importante porque o método GetXFAFormFieldNames enumera os campos como caminhos delimitados por barras que servem de entrada direta para as APIs de valor e de modelo. Os índices de ocorrência são baseados em zero, conforme a especificação, de modo que Detail[0] representa a primeira linha. Duas regras de escape mantêm nomes incomuns endereçáveis: em um caminho delimitado por pontos, \. indica um ponto literal (Line\.Item), enquanto caminhos delimitados por barras tratam pontos como caracteres comuns. Dados de negócios que trazem seu próprio prefixo de namespace XML são correspondidos pelo nome local, de modo que <m:Receipt> ainda responde a Receipt
var
Lib: TPDFlib;
Tax: WideString;
begin
Lib := TPDFlib.Create;
try
Lib.LoadFromFile('vat-return.pdf', '');
// Endereços equivalentes para o mesmo nó de dados
Tax := Lib.GetXFAFormFieldValue('Receipt.Tax');
Tax := Lib.GetXFAFormFieldValue('$data.Receipt.Tax');
Tax := Lib.GetXFAFormFieldValue('xfa.datasets.data.Receipt.Tax');
Tax := Lib.GetXFAFormFieldValue('$record.Tax');
// Formato com barra e índice de ocorrência baseado em zero
Lib.SetXFAFormFieldValue('$record/Detail[0]/Total_Price', '251.00');
Lib.SaveToFile('vat-return-updated.pdf');
finally
Lib.Free;
end;
end;
Como preencher linhas repetidas em um formulário XFA programaticamente?
O curinga de ocorrência [*] é a ferramenta para processamento em lote. Enquanto um índice numérico seleciona um elemento irmão, [*] seleciona todos os irmãos com o mesmo nome, de modo que $data.Receipt.Detail[*].Total_Price aponta para o campo de preço de todos os itens de linha de uma só vez. Em uma leitura, o método GetXFAFormFieldValue retorna os valores de todos os nós correspondentes unidos por um separador |; em uma gravação, o SetXFAFormFieldValue atualiza todos os nós correspondentes com o mesmo valor e retorna 1 em caso de sucesso. Um caminho que não corresponde a nada retorna uma string vazia, o que é uma maneira simples de verificar se uma ramificação existe antes de gravar nela
var
Lib: TPDFlib;
Prices: WideString;
begin
Lib := TPDFlib.Create;
try
Lib.LoadFromFile('invoice.pdf', '');
// Todas as linhas Detail de uma só vez: '250.00|60.00'
Prices := Lib.GetXFAFormFieldValue('$data.Receipt.Detail[*].Total_Price');
// Redefine uma coluna em cada linha repetida em uma única chamada
Lib.SetXFAFormFieldValue('$data.Receipt.Detail[*].Surcharge', '0.00');
Lib.SaveToFile('invoice-updated.pdf');
finally
Lib.Free;
end;
end;
Seletores descendentes, curingas de filhos e seletores de pai
Três seletores estruturais cobrem os casos nos quais você conhece o nome do campo, mas não a sua profundidade exata. O seletor descendente .. faz a correspondência em qualquer profundidade abaixo do nó atual: $data..Total_Price encontra o primeiro Total_Price em qualquer lugar sob a raiz de dados, e uma gravação por meio de um caminho descendente atualiza esse primeiro nó correspondente. Observe o limite: um índice de ocorrência após uma correspondência descendente não se desloca entre ramificações irmãs, portanto se $data..Total_Price[0] for resolvido dentro da primeira linha de Detail, $data..Total_Price[1] retornará vazio em vez de saltar para a próxima linha; use Detail[*] quando quiser selecionar todos. O curinga de filhos .* corresponde a cada filho direto e permite filtrar os segmentos restantes: $data.Receipt.*.Total_Price alcança o Total_Price dentro de cada ramificação filha de Receipt sem selecionar também um campo de resumo com o mesmo nome posicionado diretamente no próprio Receipt. Por fim, a propriedade parent sobe um nível, o que a especificação XFA 3.3 separa deliberadamente de ..: $data.Receipt.Detail[1].parent.Tax começa no segundo item de linha, sobe para Receipt e chega ao seu valor irmão Tax
Seletores de classe e predicados: #dataGroup, #dataValue, .[expression]
A sintaxe #class aborda nós do DOM de Dados por classe de objeto em vez de por nome, o que é o caminho prático quando as tags XML contêm caracteres difíceis de escrever como nomes de script. A losLab PDF Library mapeia #dataGroup para elementos que têm elementos filhos e #dataValue para elementos folha (sem filhos), e ambos se combinam com ocorrências numéricas ou [*]: $data.Receipt.#dataGroup[0].#dataValue[0] seleciona o primeiro valor dentro do primeiro grupo sob Receipt. O seletor de predicado .[expression] filtra irmãos de mesmo nome pelo conteúdo. Os predicados suportados são comparações simples de um valor filho contra um literal, com operadores relacionais como >, < e <=; quando um predicado corresponde a várias linhas, as leituras retornam unidas por | e as gravações atualizam todas as correspondências
// Sinaliza cada linha cujo Total_Price exceda 200
Lib.SetXFAFormFieldValue(
'$data.Receipt.Detail.[Total_Price > 200].Review_Flag', '1');
// Descrições de todas as linhas de baixo valor, unidas por '|' quando várias corresponderem
Desc := Lib.GetXFAFormFieldValue(
'$data.Receipt.Detail.[Total_Price <= 200].Description');
// Endereçamento baseado em classe quando os nomes das tags dificultam a escrita do script
Total := Lib.GetXFAFormFieldValue(
'$data.Receipt.#dataGroup[0].#dataValue[0]');
Quais recursos do XFA SOM não são suportados?
O suporte a SOM na losLab PDF Library está limitado a caminhos de campos de datasets e de modelos (template), e os limites são explícitos em vez de baseados no melhor esforço. Conhecê-los com antecedência evita a depuração de um caminho que simplesmente retorna uma string vazia
- Os predicados não executam FormCalc ou JavaScript arbitrário: sem chamadas de função (um predicado
contains(...)retorna vazio), sem expressões booleanas compostas, apenas uma comparação simples defilho operador literal - O
$recordfunciona apenas em pacotes sem processamento de registro; paginação de registro dataWindow e rotação de grupo de registro não estão implementadas - A resolução é feita diretamente contra o DOM de Dados e DOM de modelo; a resolução relativa do DOM de Formulário (Form DOM), a visualização mesclada que um visualizador cria em tempo de execução, não é realizada
- A semântica de carregamento de atributos do DOM de Dados não é aplicada; a biblioteca lê o pacote datasets como XML bruto, de modo que os caminhos endereçam elementos, não atributos promovidos a nós
Esses limites raramente afetam o trabalho de preenchimento em lote, porque um preenchedor endereça os dados pela estrutura em vez de depender de lógica de script. Quando afetam, a alternativa é o nível de pacote: leia todo o XML de datasets, transforme-o no Delphi e grave-o de volta
Gravando o pacote completo com SetXFAFromString
O método SetXFAFromString é esse ponto de entrada em nível de pacote: ele instala uma string XDP completa, modelo e datasets juntos, criando o contêiner AcroForm em um novo documento quando nenhum existe ainda, e aceita pacotes codificados em UTF-16 com marcas de ordem de byte (BOM), bem como UTF-8. Um padrão comum de produção é manter o XDP emitido pela agência como um elemento fixo, carregá-lo com o SetXFAFromString e depois executar chamadas SetXFAFormFieldValue com endereçamento SOM para os valores voláteis de cada fatura antes de salvar. Como o XFA é um dos dois modelos de formulário que um PDF pode conter, o lado clássico do AcroForm tem sua própria história de automação, abordada no artigo sobre ações de formulário interativo e JavaScript
As APIs de valor de campo, enumeração e pacote XFA mostradas aqui são fornecidas na losLab PDF Library para Delphi, C# e VB.NET; a página do produto contém a referência completa para manipulação de formulários