Documentation
¶
Overview ¶
Package xfa reads Adobe's XML Forms Architecture — the form description carried inside a PDF whose pages are a placeholder.
Why this exists ¶
A PDF form comes in two kinds, and only one of them is a PDF form. Measured over 2 240 real government forms: 560 carry an XFA package, and 546 of those are STATIC — a second, proprietary description of a form that is already drawn on the pages, which go-pdfkit/forms reads and fills without help.
The other fourteen are DYNAMIC. Their pages hold a panel reading "Please wait... your PDF viewer may not be able to display this type of document", and the form exists only as XML, laid out when the document is opened. Adobe removed the format from PDF 2.0; no browser and no other reader lays one out. A person handed such a file has a document that looks blank and is not.
What it does, and what it will not ¶
This reads the template — the description of the form — into a typed tree, and the datasets, which hold what has been filled in. It does not run the forms' scripts, and that is a measured decision rather than a shortcut: of 5 744 scripts across those fourteen forms, 5 131 are handlers for enter, exit, change and click. They fire when somebody types. Only 188 run at load, so the form a reader first sees does not depend on them.
FormCalc, XFA's other scripting language, is not read either: every script in the corpus declares itself as JavaScript.
Joining a field to its value ¶
Bind does that, and it is not a matter of matching names. A form binds its fields to its data explicitly, through <bind> elements — 26 573 of them across the 560 XFA packages in the corpus — and where it does not, each container consumes the next unclaimed data node of its own name, in document order. The two trees are named differently on purpose: the template names a field for the layout and the data names it for the record.
Taking the fourteen dynamic forms and asking how many template paths the data happens to answer:
cerfa_12064 212 fields, 212 values, 0 paths in common t657-fill-25e 459 fields, 459 values, 0 paths in common cerfa_12818 72 fields, 51 values, 47 paths in common CA-27_sample 136 fields, 99 values, 69 paths in common
The same count of each and not one path in common is what settles it: two of these forms name every field twice over, and only the binding says which goes with which. Guessing by name would answer confidently and wrongly.
Measured over all 560 packages:
560 templates read, 560 with a data tree
26 573 <bind> elements: 11 875 match="none"
11 544 match="global"
2 589 with no match, which means "once"
565 match="dataRef", every one with a ref
80 482 fields placed, 60 865 of them bound to a data node
0 <bind> expressions holding a construct this does not read
Values and FieldNames remain what they were — what the data says, and what the template says, each on its own — for a caller that wants one side without the other.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func FieldNames ¶ added in v0.2.0
FieldNames are the paths the TEMPLATE gives its fields, in the order it places them.
The path is the template's, and it is NOT necessarily the path the data uses. XFA binds a field to its data explicitly, through <bind> elements — one real form here carries 222 of them — and a form may name its root subform one thing while its data calls it another. Joining the two by path is right for a form that binds implicitly and wrong for one that does not, which is why this does not try: Bind does the joining properly, and hands back each field with the value actually bound to it.
Fields are looked for inside page areas as well as inside subforms. That is not a nicety: of the 212 fields in one real form, every one sits under a pageArea, and a walk that followed only subforms found none of them.
func Values ¶ added in v0.2.0
Values are what a form has been filled with, by the whole name of each field: "form1.Page1.Line101".
The names are joined with dots, as XFA's own scripting language names them, and the root of the data tree is included because a package may hold more than one form.
A field a person has not filled in has an empty value rather than no entry. The difference matters: "this field exists and is empty" and "there is no such field" are different answers, and a caller checking whether a form is complete needs the first.
Types ¶
type Binding ¶ added in v0.3.0
type Binding struct {
// Name is the field's own name, as the template writes it.
Name string
// Path is where the FORM puts it: dotted names from the root, with an
// index — "Row[2]" — on the second and later of repeated siblings. A
// nameless container contributes nothing to it.
Path string
// Kind is "field" or "exclGroup".
Kind string
// Node is the template element.
Node *Node
// Data is the data element it was bound to, or nil.
Data *Node
// DataPath is where that element sits in the DATA, which is a different
// tree and often a differently named one. Empty when nothing was bound.
DataPath string
// Value is what the data says, trimmed. Empty when nothing was bound.
Value string
// Bound says a data node was found. A false here with a non-empty form is
// the ordinary state of a blank document, not an error.
Bound bool
}
A Binding is one value-carrying container of a form — a field, or the exclusion group a set of radio buttons shares — joined to the data node it draws its value from.
Every such container appears, filled or not. Bound says which: a field the form places but the data never mentions has Bound false and an empty Value, and that is a different answer from a field bound to an empty string. A caller checking whether a form is complete needs the difference.
type Measure ¶
type Measure float64
A Measure is a length, in points.
XFA writes lengths as a number and a unit — "62mm", "9pt", "0.25in" — and mixes them freely within one form: the corpus's simplest template gives its page in points, its content area's origin in inches and its fields' widths in millimetres, all in the same subform.
func ParseMeasure ¶
ParseMeasure reads a length. A bare number is points, which is what the specification says and what the corpus writes for a page's height.
type Node ¶
type Node struct {
// Kind is the element's name, without its namespace: "subform", "field",
// "draw", "pageArea".
Kind string
// Attr are its attributes, by name without namespace.
Attr map[string]string
// Text is the character data directly inside it, with surrounding space
// trimmed. A <text> element's caption lives here.
Text string
// Kids are the elements inside it, in order. Order is not decoration: a
// subform laid out top-to-bottom places its children in it.
Kids []*Node
}
A Node is one element of a template, with the children it contains.
The tree is kept whole rather than reduced to the elements this package understands today. XFA has some hundreds of element types and a form uses a few dozen; throwing the rest away at parse time would mean re-parsing to add each one, and would lose the ordering that layout depends on.
func ParseDatasets ¶ added in v0.2.0
ParseDatasets reads the datasets part of an XFA package — what has been filled in — and returns the data under it.
The tree mirrors the template's names. A form whose root subform is called form1 and whose field is called Number1 keeps its value at <data><form1><Number1>1.00</Number1></form1></data>, which is why the values can be found without laying the form out.
What comes back is the <data> element, not the <datasets> wrapper: the wrapper carries the package's plumbing, and every path into the form starts below it.
func ParseTemplate ¶
ParseTemplate reads the template part of an XFA package.
It is lenient in one direction and strict in the other. Namespaces are dropped, because a template writes the same element under xfa-template and under no namespace at all depending on who produced it, and the difference carries nothing. Processing instructions and comments are skipped: a template written by Adobe's designer carries hundreds of them recording what the designer did, which is not part of the form.
A document whose XML does not close is refused. Half a template is not a form, and laying one out would put half a form on paper without saying so.
func (*Node) Child ¶
Child is the first child of that kind, or nil. Most of the template is singular — a field has one ui, one border, one caption — so looking one up by name is how it is read.
type Result ¶ added in v0.3.0
type Result struct {
// Fields are the form's value-carrying containers, in the order the form
// places them.
Fields []Binding
// Unsupported are the <bind> expressions that were not followed. Each one
// leaves its field unbound.
Unsupported []Unsupported
// Truncated says binding stopped early because the form produced more
// containers than this package will hold. It is a defence against a file
// built to make a reader work forever, not something a real form does.
Truncated bool
}
A Result is what a form holds once its template and its data are joined.
func Bind ¶ added in v0.3.0
Bind joins a template to the datasets that fill it and returns each of the form's fields with the value bound to it.
How a field finds its value ¶
XFA does not put the value under the field's own name. The template names a field for the layout and the data names it for the record, and the two need not agree — of fourteen dynamic forms measured here, two have the same count of fields and values and not one path in common. What joins them is either an explicit <bind ref="..."> or, far more often, a walk in which each container consumes the next unconsumed data node of its own name. Both are implemented, following Mozilla's pdf.js, which is the only complete free implementation of this.
The <bind> element's match attribute is honoured in all four forms: once (the default), global, dataRef and none.
Which SOM expressions are read ¶
A ref is a SOM expression. These forms are read:
Name.Sub.Leaf a path from the current data node, walking up to the
parent and its parents if the first name is not found
Name[2] the third sibling of that name
Name[*] every sibling of that name
$data.Name rooted at <data>
$record.Name rooted at the first record under <data>
$.Name rooted at the current data node
!.data.Name rooted at <datasets>
Parent..Leaf Leaf anywhere below Parent
Parent.#field by element name rather than by name attribute
Name.attr an attribute reads like a child
These are NOT read, and each one is reported in Result.Unsupported rather than guessed at:
Name.[expr] a FormCalc subexpression Name.(expr) a JavaScript predicate Name[-1] a negative index $template, $form, $layout, $host, $event, $connectionSet, $dataWindow, $xfa references outside the data, which name no value
Measured over 560 real templates carrying 26 573 <bind> elements, none of the unsupported forms occurs: every ref is a plain path, a $record path, a $ path, or one of those with [*].
Where this deliberately differs from pdf.js ¶
It does not create data nodes. pdf.js, meeting a ref that matches nothing, invents the nodes the expression describes so that a person typing into the form has somewhere to put the answer. This reads a document rather than editing one, so the field is simply reported unbound — which is the same answer, since an invented node is empty.
It does not repeat a container to satisfy occur's initial count. pdf.js, merging into empty data, clones a subform until there are as many as the template asks for. Those clones carry no data; they change the layout, not the values, and this returns values.
It descends into pageSet and pageArea. pdf.js does not bind their contents at all, because it lays them out separately — but of one real form's 212 fields, every single one sits under a pageArea, and dropping them silently would lose the whole form.
It stops a global search that has begun to repeat. pdf.js's global lookup does not skip nodes it has already taken, so a container with no upper occur bound and match="global" asks for the same node forever; this stops at the repeat.
A nil template gives an empty result; a nil data tree binds nothing, which is what a document nobody has filled in should say.
func (*Result) Values ¶ added in v0.3.0
Values are the bound fields by their form path. Fields that were not bound are left out, so a caller filling a document gets what the data actually says and nothing invented.
A path repeated by two nameless branches keeps the first. Use Result.Fields where that matters; it keeps every one, in order.
type Unsupported ¶ added in v0.3.0
type Unsupported struct {
// Field is the form path of the container carrying the <bind>.
Field string
// Ref is the expression as written.
Ref string
// Why names the construct, in words fit to show somebody.
Why string
}
An Unsupported records a <bind> whose expression this package met and did not follow. It is reported rather than swallowed: a binder that quietly differs from Adobe on one construct is worse than one that says where it stops, because the caller cannot tell a field nobody filled in from a field whose value was not looked for.