pyRegTab
RegTab: pattern-driven data extraction from document tables with regular structure — the Python port of jRegTab with a native Rust core.
pyRegTab compiles RTL (Regular Table Language) patterns into abstract table patterns (ATP), matches them against a table's syntactic layer (ITM), and interprets the match into a relational recordset:
TableSyntax → RtlCompiler/TablePattern → AtpMatcher → TableInterpreter → Recordset
pyRegTab 0.7.1 ≙ jRegTab 0.7.1 (same API, same semantics, same test
corpus), including the
embedded RTL DSL pyregtab.dsl — a port of jRegTab's ru.icc.regtab.dsl
(added upstream in jRegTab 0.3.0). Python-side extras on top of the Java API:
AtpMatcher.match_many (parallel batch matching), Recordset.to_pandas(),
Recordset.to_csv(), RtlCompileError.line/.col attributes,
CellDerivedItem.span (the item's byte range within the raw cell text), and
the CLI runner python -m pyregtab.runner (see below).
Since 0.5.1 the language and the interpreter follow jRegTab 0.6.0–0.7.1:
JOIN(K) is the record product (a cross product for K = ∅, an equi-join
on the key K otherwise), the former folding operation is spelled
CONCAT(K) — a pattern written for 0.5.x must replace JOIN(K) by
CONCAT(K); the key K may name attributes (CONCAT(0, 1, 'A', 'B'),
JOIN('Year')); skipped CONCAT/JOIN actions (a key mismatch, a shared
attribute, a forgotten REC) are reported through
TableInterpreter.diagnostics(); zero-width subrows match the empty
sequence and {0}/{1} are accepted; cell-derived providers use a spatial
index, so interpretation is linear in the number of cells (a 1.19 M-cell
table interprets in seconds).
Installation
pip install pyregtab
Binary wheels are published for Windows, Linux and macOS (x86-64 / arm64),
CPython ≥ 3.10 (one abi3 wheel per platform). Building from the sdist
requires a Rust toolchain.
Example
from pyregtab import TableSyntax, RtlCompiler, AtpMatcher, TableInterpreter
syntax = TableSyntax(3, 3)
syntax.cell(0, 1).set_text("CA"); syntax.cell(0, 2).set_text("HU")
syntax.cell(1, 0).set_text("IKT"); syntax.cell(1, 1).set_text("0 Jan"); syntax.cell(1, 2).set_text("8 Feb")
syntax.cell(2, 0).set_text("SVO"); syntax.cell(2, 1).set_text("31 Jan"); syntax.cell(2, 2).set_text("40 Feb")
pattern = RtlCompiler.compile("""
[ [] [VAL : 'AIRLINE'->AVP]+ ]
[ [VAL : 'AIRPORT'->AVP]
[VAL : (COL, ROW, CL)->REC, 'ND'->AVP " " VAL : 'MON'->AVP]+ ]+
""")
itm = AtpMatcher.match(pattern, syntax) # InterpretableTable | None
rs = TableInterpreter().interpret(itm) # Recordset
rs.schema.attributes # ['ND', 'AIRLINE', 'AIRPORT', 'MON']
rs[0]["ND"] # '0'
df = rs.to_pandas() # extras: pip install pyregtab[pandas]
Patterns can also be built without RTL, via the fluent spec API
(TablePattern.of(SubtablePattern.of(...)) — same factories as in Java,
snake_case method names), and serialized back to RTL with
AtpToRtlSerializer.serialize(pattern).
For a terser, RTL-like way to build patterns in code, use the embedded RTL
DSL (pyregtab.dsl) — see Embedded RTL below.
Named Python predicates are attached to RTL via EXT('name'):
from pyregtab import Bindings
p = RtlCompiler.compile(
"{ [ [EXT('isTotal') ? VAL : ST*->REC] []+ ] }+",
Bindings.of().cell("isTotal", lambda cell: cell.text.startswith("Total")),
)
Embedded RTL
The pyregtab.dsl module is a fluent DSL that reads almost like RTL but is
ordinary Python — with IDE completion, structural typing, pattern composition
via plain variables, and Python callables as escape-hatch constraints. It builds
the same TablePattern objects as the compiler (verified byte-for-byte
against RtlCompiler.compile for a representative set of tasks in
tests/test_dsl.py).
from pyregtab.dsl import *
# RTL: { [ [VAL : ST*->REC] [VAL]{2} []+ ]
# [ [] [VAL]{4} []+ ] }+
p = table(
subtable(
row(cell(VAL, rec(ST.unbounded())), cell(VAL).exactly(2), skip().one_or_more()),
row(skip(), cell(VAL).exactly(4), skip().one_or_more()),
).one_or_more())
Method names are snake_case (.one_or_more(), .and_(), .split_by()); the
vocabulary constants (VAL, ST, COL, C(n), …) match RTL. See the
Embedded RTL guide for the full mapping and the
where(...) escape hatch.
API mapping (Java → Python)
| Java | Python |
|---|---|
RtlCompiler.compile(String) |
RtlCompiler.compile(str) / pyregtab.compile(...) |
AtpMatcher.match(p, s) → Optional<InterpretableTable> |
AtpMatcher.match(p, s) → InterpretableTable | None |
Quantifier.oneOrMore() |
Quantifier.one_or_more() |
new TableInterpreter().withStrategy(s).interpret(itm) |
TableInterpreter().with_strategy(s).interpret(itm) |
rs.records().get(0).get("Name") |
rs[0]["Name"], rs.records, record.get("Name") |
cell.text() / cell.setText(t) |
property cell.text (get/set); cell.set_text(t) also works |
RtlCompileException |
RtlCompileError |
Architecture
Everything after the Python call boundary runs in a native core written in
Rust (pyregtab._core, built with PyO3 and
maturin); the Python layer is a thin re-export.
grammar/RTL.g4— the normative specification of the RTL language (a verbatim copy from jRegTab; the upstream commit and the grammar's SHA-256 are recorded ingrammar/UPSTREAM). The core's parser is a hand-written lexer + recursive descent that structurally follows the grammar rules. A CI job (tools/check_grammar_sync.py) fails the build if the copy drifts from the pinned hash, and — when a jRegTab read token is available — cross-checks it byte-for-byte against the upstream commit.conformance/— the shared RTL conformance corpus (also pinned from jRegTab, seeconformance/UPSTREAMandconformance/README.md). Both implementations must compile every positive case to the same canonical form and reject every negative case; the corpus runs in CI of both projects. Any RTL language change flows:RTL.g4in jregtab → corpus extension → both parsers → green corpus in both CIs.- Regular expressions in RTL constraints are executed by the Rust
regexcrate (linear-time). The reference fixture corpus uses no lookaround/backreferences (audited), so the dialect is compatible withjava.util.regexon this corpus. Documented divergences from Java:\d/\s/\ware Unicode-aware inregex(ASCII in Java), andSUBSTRindices count code points (UTF-16 units in Java) — identical behavior on the entire reference corpus.
Testing
pytest tests runs (1 981 tests):
- the full benchmark suite — tasks 001–150 (Foofah, RegTab, Baikal),
every fixture variant, both via RTL patterns and via ATP patterns
built with the Python spec API (1 500 task variants in total; fixtures
are copied verbatim from jRegTab into
tests/fixtures/tasks, ATP builders are mechanically translated from the Java tests bytools/translate_atp.py); - embedded RTL DSL parity — 26 representative tasks/constructs built with
pyregtab.dslproduce byte-identical ATP toRtlCompiler.compile(tests/test_dsl.py); - the RTL conformance corpus (positive canonical forms, fixed points, negative rejections);
- RTL↔ATP round-trip for tasks 001–050;
- API unit tests (syntax layer, extractors, EXT bindings, custom
predicates, transformations, interpreter options, GIL-released batch
matching from a thread pool and via
AtpMatcher.match_many); CONCAT/JOINsemantics, named keys, diagnostics, strict preconditions, zero-width subrows and{0}/{1}(tests/test_join_concat.py, a port of jRegTab'sTableInterpreterMultiRecordTest).
cargo test additionally runs the conformance corpus, an end-to-end smoke
test against the native core alone, the working-state unit tests for
CONCAT/JOIN (ports of WorkingStateConcatTest/WorkingStateJoinTest), the
zero-width matcher cases, and a randomized equivalence test of the indexed
provider against the reference definition Υ^{J,k}_{τ,κ} (full scan + sort).
Differential testing against the Java reference (tools/differential.py +
tools/RecordsetDumpMain.java) compares recordsets cell-by-cell on all 750
task variants; the 244 ATBench solutions of regtab-eval-on-atbench are
byte-identical between the jRegTab 0.7.1 runner and python -m pyregtab.runner.
IDE support
Install Regular Table Language (RTL)
from the VS Code Marketplace (ext install regtab.regtab): syntax highlighting
for .rtl files and for RTL embedded in Python strings passed to
RtlCompiler.compile(...), plus compile diagnostics and a live match preview
against CSV fixtures. The extension sources are at
regtab/vscode-rtl; a TextMate bundle for
IntelliJ/PyCharm and other TextMate editors is under ide/.
RTL is also validated at compile time: RtlCompiler.compile(...) raises
RtlCompileError with a line:col position on an invalid pattern.
Command-line runner
python -m pyregtab.runner <solution.rtl> (or python -m pyregtab <solution.rtl>)
reads ./input.csv, applies the pattern (RtlCompiler.compile →
AtpMatcher.match → TableInterpreter with RECORD_FIRST →
pattern.transform) and writes ./output.csv — the schema as the header row,
every field double-quoted, a missing value as the empty string, UTF-8, LF. It
is the drop-in counterpart of the regtab-runner jar used by
regtab-eval-on-atbench:
same I/O contract, same exit codes (0 — success, 1 — the pattern did not
match, 2 — invalid usage), same CSV parsing (RFC 4180 with multi-line quoted
cells, ragged rows padded); on the 244 ATBench solutions of that project the
two runners produce byte-identical output.csv. --input/--output override
the file names, --strict turns interpreter diagnostics into errors.
Development
python -m venv .venv && . .venv/bin/activate # or .venv\Scripts\activate
pip install maturin pytest
maturin develop --release
pytest tests -q
License
MIT
Metadata
Release files for pyregtab 0.7.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Built distributions (wheels)
| File | Reset | |||
|---|---|---|---|---|
| pyregtab-0.7.1-cp310-abi3-win_amd64.whl | CPython 3.10 | abi3 | Windows x86-64 | Details |
| pyregtab-0.7.1-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl | CPython 3.10 | abi3 | Linux glibc 2.17+ x86-64 | Details |
| pyregtab-0.7.1-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl | CPython 3.10 | abi3 | Linux glibc 2.17+ ARM64 | Details |
| pyregtab-0.7.1-cp310-abi3-macosx_11_0_arm64.whl | CPython 3.10 | abi3 | macOS 11.0+ ARM64 | Details |
| pyregtab-0.7.1-cp310-abi3-macosx_10_12_x86_64.whl | CPython 3.10 | abi3 | macOS 10.12+ x86-64 | Details |
Total release size: 6.8 MB
Release files / pyregtab-0.7.1-cp310-abi3-win_amd64.whl
| Download URL | pyregtab-0.7.1-cp310-abi3-win_amd64.whl |
|---|---|
| Size | 1.3 MB |
| Tags | CPython 3.10 Windows x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
b13e0a1ccdaa1aca4229b0f8485c01a4d1e82c2b9f1cd44f77577e62848172cc
|
|
BLAKE2b-256 checksum How to use checksums |
929be268f52b4e0770ec9017a4f2800579fb1989c6a0a7506a8cb13b31de6a79
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
maturin/1.15.0
|
Release files / pyregtab-0.7.1-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
| Download URL | pyregtab-0.7.1-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl |
|---|---|
| Size | 1.4 MB |
| Tags | CPython 3.10 Linux glibc 2.17+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
edc3f6f03059aaadea8051b01c3d26b99bb26c92742ee9b538995243e90a5b2e
|
|
BLAKE2b-256 checksum How to use checksums |
a6f8107895bb41b849ef4738ba247504f27806cbbc50d5f2b20179ecc284aee9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
maturin/1.15.0
|
Release files / pyregtab-0.7.1-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
| Download URL | pyregtab-0.7.1-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl |
|---|---|
| Size | 1.4 MB |
| Tags | CPython 3.10 Linux glibc 2.17+ ARM64 abi3 |
|
SHA-256 checksum How to use checksums |
2e52d68d7e941e7fbdc3e26311d70e6b93ad096ce134bda119a4a60fea5db24c
|
|
BLAKE2b-256 checksum How to use checksums |
ce7a069d4aaa83ce2a5ecd436fe15a38175cf8ad074cc97c14f885d960a0d943
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
maturin/1.15.0
|
Release files / pyregtab-0.7.1-cp310-abi3-macosx_11_0_arm64.whl
| Download URL | pyregtab-0.7.1-cp310-abi3-macosx_11_0_arm64.whl |
|---|---|
| Size | 1.3 MB |
| Tags | CPython 3.10 abi3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
10fab663612f45f770c938e1fad59c2de61064048571f6a8a8dd0a23eef53195
|
|
BLAKE2b-256 checksum How to use checksums |
1dfde9a9b30cf58b246b928f336eef34f0e25512a17ed05d78d871388790c2db
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
maturin/1.15.0
|
Release files / pyregtab-0.7.1-cp310-abi3-macosx_10_12_x86_64.whl
| Download URL | pyregtab-0.7.1-cp310-abi3-macosx_10_12_x86_64.whl |
|---|---|
| Size | 1.4 MB |
| Tags | CPython 3.10 abi3 macOS 10.12+ x86-64 |
|
SHA-256 checksum How to use checksums |
1426391adf5ca78c5ddf459070490376ee5d6134b4bc230c844b36c214a565c8
|
|
BLAKE2b-256 checksum How to use checksums |
e8c777885486f0a55209873d900b5b1728c23dcfebf980c76e736ec7541c644d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
maturin/1.15.0
|