Introduction
If you know what FDF files are, you can skip this section.
PDF files sometimes includes form fields. These fields can be represented as FDF in plain text form, extracted using certain utilities. Important things to note about this file are:
A field is represented by
/V ()
/T (Field)
where "Field" is the field name and whatever is written within the parenthesis next to /V will become the input for the utility that will fill the form.
It is possible for there to be nested fields of arbitrary depth. A basic example is
/Kids [
<<
/V ()
/T (node1)
>>
<<
/V ()
/T (node2)
>>]
/T (root)
Here there are two fields node1
and node2
both nested under root. When fields are nested, names that are seen by a user are the components of the hierarchy, separated by .
s. For instance name of node1
is root.node1
.
The code
The big picture goal of this is to identify the lines that need to be modified when a user provides a bunch of field names (eg. Field
, root.node1
)
This code reads the FDF file and marks the lines that should be modified to fill a field of a given name. The way I do it involves iterating every line to detect the tree roots and append the name of the roots to every child. Since this will end up in CRAN I want to make sure the approach isn't overly convoluted.
fdfAnnotate = function(fdfLines){
fields = vector(length = length(fdfLines),mode= 'character')
nests = 0
# iterate over every line
for (i in seq_along(fdfLines)){
if(grepl('/T \\(',fdfLines[i])){
# /T represents a field or a root name
# take the name
name = stringr::str_extract(fdfLines[i],'(?<=\\().*?(?=\\))')
if(grepl('/V',fdfLines[i-1])){
# if the line before the naming line starts with /V
# there is no hierarhcy, just name the line
fields[i-1] = name
} else if(grepl('>>\\]',fdfLines[i-1])){
# if the line above the name is >>] the name represents a root
# start reading from the line above
z = i-2
# this keeps track of the nest levels.
# we will be reading the file backwards trying to
# reach to the end of this root
nest = 1
while(nest!=0){
if(grepl('/V',fdfLines[z])){
# if a field is found, append the name of the root to the left
# separated by a "."
fields[z] = paste0(name,'.',fields[z])
} else if(grepl('>>\\]',fdfLines[z])){
# if another nest stops, that means we are inside another root
nest = nest + 1
} else if(grepl('/Kids \\[',fdfLines[z])){
# every time a root closes reduce the nest. if you reach 0
# it means its over
nest = nest - 1
}
# go back one line in the file.
z = z - 1
}
}
}
}
data.frame(fdfLines,fields,stringsAsFactors = FALSE)
}
Usage
You can use it by doing
fdfLines = readLines([pathToFDFfile])
fdfAnnotate(fdfLines)
Below is the FDF file I use for my tests. It includes single and double layered hierarchies as well as a bunch of normal fields.
%FDF-1.2
%âãÏÓ
1 0 obj
<<
/FDF
<<
/Fields [
<<
/V ()
/T (node1)
>>
<<
/Kids [
<<
/Kids [
<<
/V ()
/T (node1)
>>
<<
/V ()
/T (node3)
>>
<<
/V ()
/T (node2)
>>]
/T (child)
>>
<<
/Kids [
<<
/V ()
/T (node1)
>>
<<
/V ()
/T (node2)
>>]
/T (child2)
>>]
/T (hierarchy2)
>>
<<
/V ()
/T (TextField1)
>>
<<
/V ()
/T (TextField2)
>>
<<
/V ()
/T (TextFieldPage2)
>>
<<
/V ()
/T (List Box)
>>
<<
/V ()
/T (TextFieldPage3)
>>
<<
/Kids [
<<
/V ()
/T (node1)
>>
<<
/V ()
/T (node4)
>>
<<
/V ()
/T (node3)
>>
<<
/V ()
/T (node2)
>>]
/T (hierarchy)
>>
<<
/V ()
/T (betweenHierarch)
>>
<<
/V /Off
/T (RadioGroup)
>>
<<
/V /Off
/T (checkBox)
>>]
>>
>>
endobj
trailer
<<
/Root 1 0 R
>>
%%EOF
This file represents the form fields in this pdf file.
Using my function, the output is:
fdfLines fields
1 %FDF-1.2
2 %âãÏÓ
3 1 0 obj
4 <<
5 /FDF
6 <<
7 /Fields [
8 <<
9 /V () node1
10 /T (node1)
11 >>
12 <<
13 /Kids [
14 <<
15 /Kids [
16 <<
17 /V () hierarchy2.child.node1
18 /T (node1)
19 >>
20 <<
21 /V () hierarchy2.child.node3
22 /T (node3)
23 >>
24 <<
25 /V () hierarchy2.child.node2
26 /T (node2)
27 >>]
28 /T (child)
29 >>
30 <<
31 /Kids [
32 <<
33 /V () hierarchy2.child2.node1
34 /T (node1)
35 >>
36 <<
37 /V () hierarchy2.child2.node2
38 /T (node2)
39 >>]
40 /T (child2)
41 >>]
42 /T (hierarchy2)
43 >>
44 <<
45 /V () TextField1
46 /T (TextField1)
47 >>
48 <<
49 /V () TextField2
50 /T (TextField2)
51 >>
52 <<
53 /V () TextFieldPage2
54 /T (TextFieldPage2)
55 >>
56 <<
57 /V () List Box
58 /T (List Box)
59 >>
60 <<
61 /V () TextFieldPage3
62 /T (TextFieldPage3)
63 >>
64 <<
65 /Kids [
66 <<
67 /V () hierarchy.node1
68 /T (node1)
69 >>
70 <<
71 /V () hierarchy.node4
72 /T (node4)
73 >>
74 <<
75 /V () hierarchy.node3
76 /T (node3)
77 >>
78 <<
79 /V () hierarchy.node2
80 /T (node2)
81 >>]
82 /T (hierarchy)
83 >>
84 <<
85 /V () betweenHierarch
86 /T (betweenHierarch)
87 >>
88 <<
89 /V /Off RadioGroup
90 /T (RadioGroup)
91 >>
92 <<
93 /V /Off checkBox
94 /T (checkBox)
95 >>]
96 >>
97 >>
98 endobj
99 trailer
100
101 <<
102 /Root 1 0 R
103 >>
104 %%EOF