9. `data::` namespace — external data
8 functions to cross-check the PDF content against your own lists and tables. Everything is local: no data leaves the machine.
9.1 Where the files live
Section titled “9.1 Where the files live”Glossaries and datasets take a path relative to the working directory:
data::load_glossary("terms/legal.txt")data::load_dataset("data/batches.csv")Lookup tables (query_gtin, query_medicamento, query_postal_code) have fixed
names and are searched for in this order:
$PDFL_DATA_DIR(environment variable)./dados/./- Profiles installed by
pdfl add(pdfl_profiles/*/dados/) - Next to the analyzed PDF
# Pointing explicitly at the data folderPDFL_DATA_DIR=/opt/databases pdfl run profile.pdfl document.pdfIf a table cannot be found, the error message says where to put it.
To ship tables together with profiles, use pdfl pack — see
chapter 11.
9.2 Glossaries
Section titled “9.2 Glossaries”A glossary is a text file with one term per line. Blank lines and lines starting
with # are ignored.
terms/required.txt:
# Terms every policy must containwaiting periodcovered benefitsgeneral conditionsdata::load_glossary(file)
Section titled “data::load_glossary(file)”Loads the glossary as a list of terms.
check "Glossary loaded" { terms = data::load_glossary("terms/required.txt") print("terms in the glossary:", terms.length) require terms.contains("general conditions")}data::validate_against_reference(file)
Section titled “data::validate_against_reference(file)”The most direct route: returns the list of glossary terms that do not appear in the document. An empty list means everything is there.
check "Mandatory clauses" { missing = data::validate_against_reference("terms/required.txt") assert missing.length == 0, "clauses missing from the policy: #{missing.join("; ")}"}Comparison ignores case and spacing — “GENERAL CONDITIONS” satisfies “general conditions”.
9.3 Datasets (CSV)
Section titled “9.3 Datasets (CSV)”data::load_dataset(file)
Section titled “data::load_dataset(file)”Loads a CSV as a list of rows; each row is a list of columns. Quotes follow the standard (a quoted field may contain commas).
data/batches.csv:
batch,description,expiryL2026-08,Approved batch August/2026,2028-08-01L2026-09,Approved batch September/2026,2028-09-01check "Walking the table" { rows = data::load_dataset("data/batches.csv")
// The first row is the header print("columns:", rows.first().join(" | ")) print("records:", rows.length - 1)
// get(n) is 1-based: get(1) is the first column rows.each { |row| print(row.get(1), "->", row.get(2)) }}data::lookup_value(file, key)
Section titled “data::lookup_value(file, key)”Looks the key up in the first column and returns the second column’s value.
Returns null when not found — and since null is falsy, you can test it
directly.
check "Approved batch" { batch = text::extract_from_region(1, region(400, 50, 150, 20)).trim()
description = data::lookup_value("data/batches.csv", batch) assert description, "batch #{batch} is not in the approved list"
print("batch recognized:", description)}9.4 Lookup tables
Section titled “9.4 Lookup tables”These functions look for fixed-name files in the folders described in 9.1 and
return the whole row as a list (or null).
data::query_gtin(code)
Section titled “data::query_gtin(code)”Queries gtin.csv. Punctuation in the code is ignored.
dados/gtin.csv:
gtin,description,manufacturer7891234567895,Dipyrone 500mg 20 tablets,Example Labscheck "Approved product" { // Cross-checking against the code read from the packaging itself code = codes::decode_barcode(1) product = data::query_gtin(code)
assert product, "GTIN #{code} is not in the product database"
print("product:", product.get(2)) print("manufacturer:", product.get(3))}data::query_medicamento(registration_or_name)
Section titled “data::query_medicamento(registration_or_name)”Queries medicamentos.csv. Accepts the registration number (first column) or
part of the name (second column).
dados/medicamentos.csv:
registration,name,active_ingredient,band1.0298.0123,Dipyrone,dipyrone monohydrate,otc1.0298.0456,Amoxicillin,amoxicillin trihydrate,prescriptioncheck "Correct band on the insert" { registration = text::extract_from_region(1, region(50, 780, 200, 15)).trim() medicine = data::query_medicamento(registration)
assert medicine, "registration #{registration} not found in the regulatory database"
// If the band is prescription-only, the mandatory text must be in the artwork band = medicine.get(4) print("medicine:", medicine.get(2), "| band:", band)
assert band != "prescription" || text::require_text("PRESCRIPTION ONLY"), "prescription medicine without the mandatory text"}data::query_postal_code(code)
Section titled “data::query_postal_code(code)”Queries ceps.csv. Accepts the Brazilian postal code with or without a hyphen;
requires 8 digits.
dados/ceps.csv:
cep,street,district,city,state01310100,Avenida Paulista,Bela Vista,Sao Paulo,SPcheck "Manufacturer address" { address = data::query_postal_code("01310-100") assert address, "postal code not found in the database"
print("street:", address.get(2)) print("city:", address.get(4), "-", address.get(5))}data::validate_address(postal_code, "fragment")
Section titled “data::validate_address(postal_code, "fragment")”Checks whether the given fragment appears in the address for that postal code.
check "Printed address matches the postal code" { // The address on the packaging must match the declared postal code assert data::validate_address("01310100", "Avenida Paulista"), "printed address does not match the given postal code"}9.5 Complete example
Section titled “9.5 Complete example”// insert_with_databases.pdfl — validation cross-checking the PDF against local data// Usage: PDFL_DATA_DIR=./databases pdfl run insert_with_databases.pdfl insert.pdfprofile "insert-with-references" {
check "Mandatory regulatory terms" tags: ["glossary"] { missing = data::validate_against_reference("databases/regulatory_terms.txt") assert missing.length == 0, "mandatory texts missing: #{missing.join("; ")}" }
check "Product in the database" tags: ["data", "critical"] { code = codes::decode_barcode(1) product = data::query_gtin(code) assert product, "GTIN #{code} not approved"
// The registered name must appear in print name = product.get(2) assert text::require_text(name), "the name '#{name}' from the database does not appear on the insert" print("product verified:", name) }
check "Registration and band" tags: ["regulatory"] { registration = text::extract_from_region(1, region(50, 780, 200, 15)).trim() med = data::query_medicamento(registration) assert med, "registration #{registration} not found"
assert med.get(4) != "prescription" || text::require_text("PRESCRIPTION ONLY"), "prescription band requires the prescription notice" }
check "Manufacturer address" tags: ["data"] { assert data::validate_address("01310100", "Avenida Paulista"), "manufacturer address does not match the postal code" }}