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, 220 values, 0 paths in common t657-fill-25e 459 fields, 459 values, 0 paths in common cerfa_12818 72 fields, 72 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
Where a field goes ¶
Expand joins the two trees and returns the form as a document holds it — one node per occurrence, so a table row written once in the template and filled three times by the data is three nodes. Layout runs over that and never over the template, which is what pdf.js's binder does too and for the same reason (bind.js:61).
Place then lays a form out on one page under POSITIONED layout: a box's place is its own x and y added to those of every container above it, down from the content area's origin, with anchorType and rotate resolved. Everything it does not reach — flow layouts, sizes only text measurement would give, other page areas — comes back in Layout.Unplaced with the reason written out, one element at a time.
Measured over the same 560 packages, and against pdf.js's own layout run over the same files:
82 386 fields in the templates, 82 378 in the expanded forms
13 331 placed, 69 047 reported unplaced
of the unplaced, 68 891 are under a flow layout
21 933 boxes compared with pdf.js, 21 881 agreeing to within 1/100 pt
47 more differing only by pdf.js's own two-decimal rounding
5 differing, every one of them a colSpan width and none a place
The gap between what is placed and the 70 297 fields whose own enclosing subform is positioned is the finding rather than the shortfall: 556 of the 560 outermost subforms are laid out "tb", so almost every positioned subform hangs below a flow one, and where a flow layout puts its second child is the flow layout itself.
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. Values names a repeated sibling the way a binding does, "Row[1]", because a form's table is repeated siblings and naming them alike was losing 10 125 of the corpus's 71 346 values.
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 Value ¶ added in v0.2.0
Value is one field's value, by its whole name. The second and later of repeated siblings are addressed by index — "form1.Row[1].Cell".
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.
Repeated siblings are numbered the way Bind numbers them and the way a SOM expression addresses them: the first of a name plain, the rest "Row[1]", "Row[2]", counting from zero. Without the numbers they collide, and a table of six rows comes back as one — 10 125 of the 71 346 values the corpus carries, across 216 of its 560 forms, were lost that way before this counted them.
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.
Every value the data carries comes back. There is one entry per leaf and one per group that carries text of its own, and no two share a key.
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 Box ¶ added in v0.5.0
type Box struct {
// Node is the occurrence this box was placed for.
Node *FormNode
// Kind is "field" or "draw".
Kind string
// Path is [FormNode.Path], repeated here so a box can be reported on its
// own.
Path string
// Rect is where it goes, absolute on the page, with anchorType and rotate
// already resolved.
Rect
// Rotate is what the template says, in degrees clockwise: 0, 90, 180 or
// 270. Rect is the box the rotated element occupies; this says how the
// content inside it is turned.
Rotate int
// Value is what the data put in it, for a bound field. Empty otherwise.
Value string
}
A Box is one element of the form, placed.
type Form ¶ added in v0.5.0
type Form struct {
// Root is the <template> element's node. Under it sit the form's
// outermost subform and, inside that, the page set.
Root *FormNode
// Unsupported are the <bind> expressions that were not followed, as
// [Bind] reports them.
Unsupported []Unsupported
// Truncated says expansion stopped early. See [Result.Truncated].
Truncated bool
}
A Form is a template and the data that fills it, joined and expanded: the tree layout runs over.
func Expand ¶ added in v0.5.0
Expand joins a template to its data and returns the form tree, with every container repeated once per record that fills it.
Why this exists beside [Bind] ¶
Bind answers "what is in this form", and a flat list of fields answers it well. Layout asks a different question — where does each one go — and the answer is in the shape: a field's place is its own x and y added to those of every container above it, and a container repeated eleven times is in eleven different places. A list of bindings has nowhere to put the eleven.
This follows pdf.js, whose binder does the same thing and for the same reason: Binder.bind returns a clone of the template (bind.js:61) into which _bindOccurrences (bind.js:385-417) inserts one copy of the container per matched data node. Layout there never sees the template either.
Where it goes further than [Bind] ¶
It descends into an exclGroup. A group of radio buttons carries one value between them, so binding stops at the group; but each button is drawn in its own place inside the group's box, so layout needs them. They appear with Bound false, which is what they are: the value belongs to the group.
It carries <draw> — the static text, lines and boxes a form is mostly made of. A draw holds no data, so Bind has nothing to say about it and does not report it. It is half of what is on the paper.
Where it stops, as [Bind] does ¶
It does not clone a container to satisfy occur's initial count when the data is empty, which pdf.js's _createOccurrences (bind.js:421-462) does. Those clones carry no data; they are blank rows on a blank form.
type FormNode ¶ added in v0.5.0
type FormNode struct {
// Template is the element this was made from. Everything the template
// says that this struct does not carry — x, y, w, h, layout, border,
// caption — is read from there.
Template *Node
// Kind is the element's name: "subform", "field", "draw", "pageArea".
Kind string
// Name is what the template calls it, which may be empty.
Name string
// Path is where this occurrence sits in the form: dotted names from the
// root, with an index — "Row[2]" — on the second and later of repeated
// siblings. It is the same path [Binding] carries, for the containers
// that carry one.
Path string
// Kids are the containers inside it, in the order the form places them.
Kids []*FormNode
// Data is the data element this occurrence was bound to, or nil.
Data *Node
// Value is what that element says. Empty when nothing was bound.
Value string
// Bound says a data node was found for this occurrence.
Bound bool
}
A FormNode is one container of the expanded form: one subform, one field, one draw, as the document actually holds it rather than as the template writes it.
The difference is repetition. A template writes a table row once and says, in its <occur>, that there may be many; the document has one row per record. Expand does that expansion, so a FormNode tree has one node per occurrence and a template tree has one per element. Two nodes cloned from the same template element share their Template and differ in their Path and their value.
type Layout ¶ added in v0.5.0
type Layout struct {
// Pages is what was laid out. This slice places one page; see [Place].
Pages []Page
// Unplaced is everything it did not place, with a reason each.
Unplaced []Unplaced
}
A Layout is a form placed on paper.
func Place ¶ added in v0.5.0
Place lays a form out on one page, under positioned layout only.
What it does ¶
Every container of the form has an x and a y, and under a positioned layout they mean what they say: the element goes there, in the frame of whatever contains it. So a box's place on the page is its own x and y added to those of every container above it, down from the content area's origin. That is what this computes, resolving anchorType and rotate on the way (pdf.js layout.js:202-259), and it is what pdf.js computes too — under "position" it emits style.left and style.top from the node's own x and y (html_utils.js:112-123), and delegates to the browser's flexbox only for the flow layouts.
What it deliberately does not do, and reports instead ¶
- Flow layouts — tb, lr-tb, rl-tb, row, rl-row, table. Under those, x and y are thrown away (html_utils.js:347-350) and the children are stacked; pdf.js hands that to CSS, so there is no reference to follow and it is the whole of the next slice. 14.1% of the corpus's fields.
- Text measurement. A field whose template writes no width has no width until its text is measured. 0.5% of the corpus's fields.
- Splitting, pagination, breaks, leaders and trailers. One page area, its first content area, and whatever does not fit hangs off the bottom.
- Borders, margins and insets. A child's origin is its parent's x and y, not the inside of its parent's border.
- colSpan. A cell in a row or a table takes its width from the parent's columnWidths rather than from its own w (html_utils.js:81-111, 330-346). Where this slice places the first child of a row, it uses the child's own w — and that is the ONLY thing pdf.js and this package disagree about over the corpus: 5 boxes of 21 933, all of them the width, none of them the place.
Each of those leaves its elements in Layout.Unplaced with the reason written out. Nothing is dropped: every field and draw of the expanded form comes back in one list or the other.
A nil form, or one with no outermost subform, lays out nothing.
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.
func (*Node) Measure ¶ added in v0.5.0
Measure reads one of a node's attributes as a length.
It answers three ways, not two, and the third is the point of it. ok is false when the attribute is not there at all; err is non-nil when it is there and is not a length. XFA's "unspecified" and its "zero" are different things — a field with no width has no width until its text is measured, which is not the same as a field nought wide — and a layout that collapses them draws the second when it meant to report the first.
pdf.js keeps the distinction as the empty string and then loses it in measureToString (html_utils.js:35-41), which turns any string, meant or mistaken, into "0px".
type Page ¶ added in v0.5.0
type Page struct {
// Width and Height are the sheet, with a landscape medium already
// swapped. Both are zero when the page area writes no medium, which
// pdf.js also declines to guess at (template.js:4108).
Width, Height Measure
// Content is the content area: where the form's body is laid out, as
// against the furniture the page area draws around it. Its origin is the
// origin of every coordinate in the body.
Content Rect
// Boxes are the elements placed on it, in the order the form places them.
Boxes []Box
}
A Page is one sheet, with what this package could put on it.
type Rect ¶ added in v0.5.0
type Rect struct {
X, Y, W, H Measure
}
A Rect is a box on the page, in points.
It is measured in XFA's own frame — from the TOP-LEFT corner, with Y increasing downwards — and not in PDF's, which counts up from the bottom left. The flip belongs to whatever draws the page, because it needs the page height to do it and because leaving it here would make every coordinate in this package disagree with the template it was read from.
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 Unplaced ¶ added in v0.5.0
type Unplaced struct {
// Node is the occurrence that was not placed.
Node *FormNode
// Kind is "field" or "draw".
Kind string
// Path is [FormNode.Path].
Path string
// Why says what stopped it, in words fit to show somebody.
Why string
}
An Unplaced is one element of the form this package did not place, and why.
Every field and draw of the expanded form is in exactly one of Page.Boxes and Layout.Unplaced. That is the point of the type: a layout that reaches four fifths of a form is useful, and one that silently drops the other fifth is not, because nothing downstream can tell a form that was laid out from a form that was half laid out.
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.