A losLab PDF Library lê e escreve nós de dados repetidos em formulários dinâmicos XFA através de expressões de caminho XFA 3.3 SOM: os métodos GetXFAFormFieldValue e SetXFAFormFieldValue suportam as raízes $data, $record e !data, caracteres de substituição de ocorrências [*], seletores descendentes e de sub-elementos genéricos, a propriedade parent, seletores de classe como #dataGroup/#dataValue e predicados simples estilo FormCalc. Isto permite que um programa em Delphi atualize todas as linhas de faturação de um formulário fiscal numa única chamada. Este artigo serve de referência de endereçamento; para transferência de conjuntos de dados entre ficheiros, consulte o artigo complementar sobre intercâmbio de dados em FDF, XFDF e XFA
O problema manifesta-se quando os formulários deixam de ser lineares (planos). Uma declaração de IVA ou alfandegária desenhada como formulário XFA dinâmico não contém doze campos ordenados de Total_Price_1 a Total_Price_12; possui, sim, um subformulário Detail declarado uma vez no DOM do modelo (template DOM) e instanciado dinamicamente de acordo com as necessidades. Os valores são guardados numa segunda árvore, o DOM de Dados (Data DOM) sob a secção datasets, onde cada item corresponde a um elemento <Detail> sob a árvore <Receipt>. Nomes de campos lineares não conseguem referenciar "o preço da terceira linha" ou "todas as linhas acima de 200". Trata-se exatamente da função que o Adobe XFA 3.3 atribui às expressões SOM, cuja sintaxe é suportada pelas rotinas de gestão de campos XFA da losLab PDF Library
Como endereçar nós de dados XFA em Delphi?
Todos os caminhos de datasets iniciam numa raiz, suportando a losLab PDF Library as sintaxes padrão: $data (atalho XFA 3.3 de xfa.datasets.data), !data (referência a xfa.datasets) e, em pacotes sem processamento de registos, $record que resolve o nó de dados primário (o primeiro elemento sob o nó data). APIs ligadas ao modelo, como SetXFAFormFieldAccess, aceitam $template e xfa.template. As secções podem ser delimitadas por pontos ou barras ($data.Receipt.Tax ou $data/Receipt/Tax); a listagem GetXFAFormFieldNames enumera os campos por caminhos separados por barras, compatíveis com as APIs de valores. O índice das ocorrências baseia-se em 0, correspondendo Detail[0] à primeira linha. Lógicas de escape garantem a leitura de nomes complexos: em caminhos por pontos, o termo \. identifica um ponto literal (Line\.Item), sendo os pontos tratados normalmente em caminhos por barras. Elementos com prefixos XML namespace são pesquisados pelo nome local, mapeando <m:Receipt> como Receipt:
var
Lib: TPDFlib;
Tax: WideString;
begin
Lib := TPDFlib.Create;
try
Lib.LoadFromFile('vat-return.pdf', '');
// Endereçamentos 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');
// Caminho por barras com índice de ocorrência baseado em 0
Lib.SetXFAFormFieldValue('$record/Detail[0]/Total_Price', '251.00');
Lib.SaveToFile('vat-return-updated.pdf');
finally
Lib.Free;
end;
end;
Como preencher linhas repetidas num formulário XFA via código?
O caráter de substituição de ocorrências [*] é a ferramenta para processamento em lote. Enquanto índices numéricos selecionam elementos específicos, [*] abrange todos os irmãos homólogos (ex: $data.Receipt.Detail[*].Total_Price referencia os preços de todas as linhas em simultâneo). Na leitura, GetXFAFormFieldValue devolve os valores correspondentes concatenados pelo separador |; na escrita, SetXFAFormFieldValue atualiza todos os nós correspondentes com o valor fornecido, devolvendo 1 em caso de sucesso. Caminhos inexistentes devolvem uma string vazia, permitindo validar a presença de ramificações antes da escrita:
var
Lib: TPDFlib;
Prices: WideString;
begin
Lib := TPDFlib.Create;
try
Lib.LoadFromFile('invoice.pdf', '');
// Todas as linhas Detail de uma vez: '250.00|60.00'
Prices := Lib.GetXFAFormFieldValue('$data.Receipt.Detail[*].Total_Price');
// Repor a zero os valores de uma coluna de todas as linhas num único comando
Lib.SetXFAFormFieldValue('$data.Receipt.Detail[*].Surcharge', '0.00');
Lib.SaveToFile('invoice-updated.pdf');
finally
Lib.Free;
end;
end;
Seletores descendentes, de sub-elementos genéricos e pai
Existem três seletores estruturais para cenários onde a profundidade do campo é desconhecida. O seletor descendente .. efetua pesquisas em qualquer nível hierárquico abaixo do nó ativo: $data..Total_Price localiza a primeira ocorrência sob a raiz data (atualizando escritas este nó específico). Nota: a utilização de índices após um seletor descendente não percorre ramificações irmãs; se $data..Total_Price[0] resolver na primeira linha de Detail, a chamada de $data..Total_Price[1] devolverá vazio (use Detail[*] para iterar). O caráter de sub-elemento genérico .* pesquisa filhos diretos mantendo os filtros seguintes (ex: $data.Receipt.*.Total_Price acede ao preço sob as ramificações de Receipt, omitindo totais guardados na raiz do objeto). Por último, o operador parent sobe um nível na hierarquia (distinguindo-se de ..): $data.Receipt.Detail[1].parent.Tax inicia no segundo item, recua para Receipt e acede à propriedade Tax homóloga
Seletores de classe e predicados: #dataGroup, #dataValue, .[expressão]
A sintaxe #class referencia nós do DOM de Dados pela classe do objeto e não pelo nome (útil quando as etiquetas XML contêm caracteres incompatíveis com identificadores de script). A losLab PDF Library mapeia #dataGroup para nós que possuam nós filhos, e #dataValue para nós folha (sem filhos), combinando-se ambos com ocorrências numéricas ou genéricas: $data.Receipt.#dataGroup[0].#dataValue[0] acede ao primeiro valor sob o primeiro grupo de Receipt. Os predicados .[expressão] filtram os dados com base no conteúdo, suportando comparações simples com operadores relacionais como >, < e <=. Se o predicado corresponder a múltiplas linhas, a leitura devolve os valores concatenados por | e a escrita atualiza todos os registos homólogos:
// Marcar todas as linhas cujo Total_Price seja superior a 200
Lib.SetXFAFormFieldValue(
'$data.Receipt.Detail.[Total_Price > 200].Review_Flag', '1');
// Descrições de linhas de baixo valor concatenadas por '|'
Desc := Lib.GetXFAFormFieldValue(
'$data.Receipt.Detail.[Total_Price <= 200].Description');
// Endereçamento baseado em classe se as etiquetas XML possuírem caracteres inválidos
Total := Lib.GetXFAFormFieldValue(
'$data.Receipt.#dataGroup[0].#dataValue[0]');
Limitações nas semânticas XFA SOM
O suporte SOM na losLab PDF Library foca-se nos caminhos de datasets e modelos (templates), com limitações claras que convém conhecer previamente para evitar caminhos inoperantes:
- Predicados não executam FormCalc ou JavaScript arbitrário: não suportam chamadas de funções (expressões com
contains(...)devolvem vazio) ou expressões booleanas complexas, apenas comparações simples do tipochild op literal - O atalho
$recordfunciona apenas em pacotes sem paginação de registos; aspetos de paginaçãodataWindowe rotação de grupos não são suportados - A resolução baseia-se diretamente no DOM de Dados e DOM do modelo (template DOM); a resolução relativa de Form DOM (combinação que o visualizador monta em tempo de execução) não é realizada
- Não se aplicam lógicas de leitura de atributos no DOM de Dados; a biblioteca lê os datasets como XML simples, pelo que as expressões mapeiam elementos e não atributos promovidos a nós
Estas limitações não afetam a maioria dos fluxos de preenchimento, visto que o processamento em lote foca a estrutura física dos dados e não lógicas de script. Em caso de necessidade de processamentos complexos, pode extrair o XML do datasets completo, tratá-lo no Delphi e reintroduzi-lo no documento
Gravar o pacote completo com SetXFAFromString
O método SetXFAFromString permite a importação de pacotes completos: carrega uma estrutura XDP (modelo e datasets), criando o contentor AcroForm se este não existir, suportando codificações UTF-8 e UTF-16. O fluxo habitual consiste em carregar o XDP base via SetXFAFromString e atualizar as coordenadas dinâmicas com chamadas a SetXFAFormFieldValue antes de gravar o ficheiro. Adicionalmente, o modelo de formulários clássico (AcroForms) possui lógicas de automatização detalhadas no artigo sobre ações em formulários e JavaScript
As rotinas de gestão de dados e pacotes XFA integram a losLab PDF Library para Delphi, C# e VB.NET