Runtime¶
The tree-walking engine that drives a transform: apply() / apply_children()
route each element through the generated _dispatch router. Every output
function receives the run's RenderContext as config; XPath is evaluated by
the context's XPathEnvironment, which is built once per document and caches
the parsed node trees. xpath_extensions loads user-defined tp:* functions —
see the XPath extensions guide.
To drive a compiled module yourself, build one environment per document and pass it to each transform:
from opm.runtime import XPathEnvironment
from opm.transform import run_transform
env = XPathEnvironment(base_uri=path.resolve().as_uri(), extensions=['my.ext'])
html = run_transform(mod, root, xpath_env=env)
Render context¶
opm.runtime.context ¶
The context one transform run passes to every output function as config.
A RenderContext replaces the plain dict the runtime used to thread
through apply, the generated _dispatch and every pmf method. It has
three kinds of content, kept apart:
- Run settings — output functions, dispatch, stylesheet, templates, the
$parametersdict, theXPathEnvironment. Fixed for the run. - Shared run state — footnotes, collected metadata, counters — in one
RunStatethat every view of the run holds by reference, so nothing is lost when a view is derived. - Per-call settings —
indent,list_type,templateand the like, which a behaviour changes for its children only.RenderContext.derivereturns a new view for that; a context is never mutated in place.
Backend-private caches (DOCX images and numbering, JSON source positions) live on the output-functions instance, which a run creates for itself.
RunState
dataclass
¶
RunState(
footnotes: list = list(),
metadata: dict[str, list[str]] = dict(),
note_counter: int = 0,
id_counter: int = 0,
)
Mutable state shared by every view of one transform run.
next_note ¶
RenderContext
dataclass
¶
RenderContext(
output: str = "web",
pmf: ProcessingModelFunctions | None = None,
dispatch: Callable = _hand_on,
apply: Callable = _apply,
apply_children: Callable | None = None,
parameters: dict[str, Any] = dict(),
xpath: XPathEnvironment | None = None,
webcomponents: bool = False,
odd_css: str = "",
normalize_text: Callable[[str], str] | None = None,
text_escape: Callable[[str], str] | None = None,
input_path: str | None = None,
docx_template: Any = None,
typst_functions: frozenset[str] = frozenset(),
models: dict | None = None,
root: Any = None,
template: bool = False,
indent: str = "",
list_type: str | None = None,
list_depth: int = -1,
list_id: int | None = None,
table_rows: list | None = None,
state: RunState = RunState(),
)
Everything a behaviour needs; see the module docstring.
derive ¶
derive(**changes) -> RenderContext
build_context ¶
build_context(
root,
options: dict | None = None,
*,
mode: str,
xpath_env: XPathEnvironment | None = None,
odd_namespaces: dict[str, str] | None = None,
**settings,
) -> RenderContext
The context a generated module's transform() runs with.
mode names the output mode; its entry in opm.output_modes
supplies the output functions and the text handling. options holds the
$parameters plus the run options in RUN_OPTIONS. xpath_env
supplies everything else XPath can see; an empty environment when omitted.
settings are RenderContext fields the module fixes: dispatch,
stylesheet and the like.
Source code in src/opm/runtime/context.py
XPath environment¶
opm.runtime.xpath_env ¶
Evaluate ODD XPath against a document, with everything it depends on bound once.
An XPathEnvironment holds what an expression may reach beyond the node
it is evaluated on: the source document's URI, the documents and collections
doc() and collection() can open, project variables and namespace
prefixes, the tp: extension modules, the $parameters map, and the node
bound as $parameters?root. It is built once per transform run. All of this
used to travel inside the $parameters dict under reserved keys and was
re-derived for every predicate: extension fingerprints, merged namespaces and
the parameters cache key are now computed when the environment is made.
The environment also owns the per-document caches — the elementpath node tree
wrapped around each lxml document, the $parameters maps, the index
fn:id() answers from — so they are freed with the run instead of accumulating in
module globals for the life of the process. XPathEnvironment.with_root
and XPathEnvironment.with_parameters return cheap views sharing those
caches, which is how a chunked document binds each chunk's source node without
rebuilding anything.
Parsed expressions stay in a bounded module-level cache (compiled_xpath):
they depend on strings only, never on a document.
DocumentCache ¶
Wrapped node trees, $parameters maps and xml:id indexes for one run.
Keyed by the lxml element object, never by id(): lxml recycles proxy
objects, so ids collide across nodes (see opm.runtime.source_map).
A wrapped tree keeps its document alive, so an entry stays valid for as
long as the cache — and with it the run's environment — exists.
Source code in src/opm/runtime/xpath_env.py
XPathEnvironment ¶
XPathEnvironment(
*,
base_uri: str | None = None,
documents: dict[str, Any] | None = None,
collections: dict[str, list] | None = None,
variables: dict[str, Any] | None = None,
namespaces: dict[str, str] | None = None,
extensions=None,
parameters: dict[str, Any] | None = None,
root: _Element | None = None,
cache: DocumentCache | None = None,
)
Everything an ODD expression can see beyond its context node.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
base_uri
|
str | None
|
URI of the source document; |
None
|
documents
|
dict[str, Any] | None
|
|
None
|
collections
|
dict[str, list] | None
|
|
None
|
variables
|
dict[str, Any] | None
|
XPath variables, keys in Clark notation ( |
None
|
namespaces
|
dict[str, str] | None
|
Project prefixes ( |
None
|
extensions
|
Dotted module paths whose public callables become |
None
|
|
parameters
|
dict[str, Any] | None
|
Bound as |
None
|
root
|
_Element | None
|
Bound as |
None
|
cache
|
DocumentCache | None
|
Shared document caches; a fresh one by default. |
None
|
Source code in src/opm/runtime/xpath_env.py
namespaces
property
¶
The prefixes in effect: the ODD's, overridden by the project's.
with_root ¶
with_root(root: _Element | None) -> XPathEnvironment
with_parameters ¶
with_parameters(
parameters: dict[str, Any] | None,
) -> XPathEnvironment
A view binding parameters as $parameters.
Source code in src/opm/runtime/xpath_env.py
for_odd ¶
for_odd(
odd_namespaces: dict[str, str] | None,
) -> XPathEnvironment
A view adding the prefixes the ODD root declares.
Prefixes in XPath inside ODD attribute values are not XML names, so the ODD cannot be the only place to declare them: project prefixes win on a clash, and bind what the ODD leaves out (variable prefixes above all).
Source code in src/opm/runtime/xpath_env.py
wrapped ¶
The elementpath document node for node's tree, built once per run.
The base URI is attached to the document node, which is what makes
document-uri() and base-uri() return the source file rather than
the empty sequence. The parser's static base URI is a separate thing:
it resolves relative arguments to doc() and collection() but
never reaches the tree, so both are supplied.
Source code in src/opm/runtime/xpath_env.py
id_index ¶
fn:id()'s index of the node tree under root, built once per run.
See build_id_index.
Source code in src/opm/runtime/xpath_env.py
compile ¶
The parsed expression for expr in the static context of node.
context ¶
An XPathContext with node as context item and everything else bound.
$parameters?root is the viewed node in the original document
(tei-publisher-lib). An explicit string root in the parameters is
left as a string.
Source code in src/opm/runtime/xpath_env.py
evaluate ¶
Raw elementpath results; raises elementpath.ElementPathError.
Source code in src/opm/runtime/xpath_env.py
test ¶
Boolean test for an ODD @predicate; an error counts as false.
Source code in src/opm/runtime/xpath_env.py
count ¶
Length of the sequence expr selects; an error counts as zero.
Source code in src/opm/runtime/xpath_env.py
select ¶
Evaluate a param @value: nodes as a list, a lone item bare.
count(ancestor::div) or string(.) give a single atomic; a path
gives lxml elements. An error gives the empty sequence.
Source code in src/opm/runtime/xpath_env.py
select_all ¶
Like select, but always a list.
Source code in src/opm/runtime/xpath_env.py
select_or_node ¶
Like select, but node itself when expr is unconfigured.
Used for ODD params that read collection() or an external variable
($global:register-root and friends), which resolve only once the
project configures them. Without that configuration the expression
raises one of UNCONFIGURED_CODES, and the fallback to the
context node is what the bundled teipublisher.odd relies on for its
in-document listPerson register. Any other error gives the empty
sequence, so a broken expression is not replaced by the whole element.
Source code in src/opm/runtime/xpath_env.py
resolve_element ¶
The one element expr selects with document_root as context.
Raises:
| Type | Description |
|---|---|
ValueError
|
expr is invalid, or does not select exactly one element. |
Source code in src/opm/runtime/xpath_env.py
current_environment ¶
current_environment() -> XPathEnvironment | None
The environment evaluating the expression that is running right now.
For functions that need the run's caches while XPath runs, such as
fn:id() and tp:source-node. None outside an evaluation.
Source code in src/opm/runtime/xpath_env.py
params_cache_key ¶
Hashable, order-independent form of a $parameters dict.
default_element_namespace_uri ¶
Namespace URI used for unprefixed element names in XPath.
XPath 3.1 binds unqualified names to this URI; TEI div lives in
http://www.tei-c.org/ns/1.0, not the empty namespace, so parent::div
only matches after setting this. Empty string means no default.
Source code in src/opm/runtime/xpath_env.py
compiled_xpath
cached
¶
compiled_xpath(
expr: str,
default_element_ns: str = "",
ext_fp: str = "",
namespaces: frozenset[tuple[str, str]] | None = None,
base_uri: str | None = None,
)
Parse each distinct expression and static context once.
Source code in src/opm/runtime/xpath_env.py
normalize_extensions ¶
Extension modules as a tuple of non-empty dotted paths.
Source code in src/opm/runtime/xpath_env.py
extension_fingerprint ¶
Cache-key fragment for extensions: module paths plus source mtimes.
Source code in src/opm/runtime/xpath_env.py
clear_compiled_xpath_cache ¶
Drop parsed expressions and loaded extension modules (e.g. between tests).
XPath errors¶
An expression that fails at run time counts as false or empty. Wrap a run in
collect_xpath_errors() to see which ones failed.
opm.runtime.xpath_diagnostics ¶
Collect the XPath errors raised while documents are transformed.
A predicate that raises is treated as false and a param as empty: the processing
model has to carry on, and an ODD shared with TEI Publisher may hold expressions
only one of the two runtimes can evaluate. The ones the compiler can recognise
never reach the runtime (opm.odd_compiler.expression_check). What is
recorded here is what is left, in two kinds:
- Hints — the project configuration is missing something the expression
needs: an undeclared prefix, an unset variable, an unregistered
tp:function, an unknown collection. Each is recorded once. - Failures — everything else: a cast on bad data, a type error, a mistake in a config-supplied XPath. Recorded once per expression and error code, with a count and the location of the first occurrence.
Collection is opt-in and scoped. with collect_xpath_errors() as log: records
what is evaluated inside the block, in the current thread or task only; with no
block active, recording is a no-op. The CLI wraps each command in one.
XPathFailure
dataclass
¶
XPathFailure(
expression: str,
code: str,
message: str,
count: int = 0,
element: str | None = None,
document: str | None = None,
line: int | None = None,
)
One expression that raised at run time, and where it first did.
XPathErrorLog
dataclass
¶
XPathErrorLog(
failures: dict[tuple[str, str], XPathFailure] = dict(),
hints: dict[str, str] = dict(),
)
Everything collect_xpath_errors saw.
ordered_failures ¶
ordered_failures() -> list[XPathFailure]
collect_xpath_errors ¶
collect_xpath_errors() -> Iterator[XPathErrorLog]
Record the XPath errors raised inside the with block.
Source code in src/opm/runtime/xpath_diagnostics.py
record_xpath_error ¶
record_xpath_error(
expression: str,
exc: Exception,
node: _Element | None = None,
base_uri: str | None = None,
) -> None
Hand one error to the active log; does nothing outside a collection block.
Source code in src/opm/runtime/xpath_diagnostics.py
Processing-model runtime¶
opm.runtime.pm_runtime ¶
Processing-model runtime: apply / apply-children and the node helpers.
Used by ODD-generated modules and runtime helpers. A run's settings and state
travel in a RenderContext (config), and its
XPath is evaluated by the context's
XPathEnvironment.
tag ¶
Local name for node.
lxml comments, PIs, and entities use a Cython factory object as .tag, not a
string, so etree.QName cannot be used on them directly.
Source code in src/opm/runtime/pm_runtime.py
template_config ¶
config as seen inside a pb:template.
A behaviour combined with a pb:template receives the already-rendered
template nodes as its content, so apply and apply_children
must hand them straight on instead of dispatching them again. Mirrors
map:entry("template", true()) in model.xql, which is what stops
tei-publisher-lib from reprocessing template output.
Without it an ODD whose schemaSpec has ns="" (JATS, and any other
vocabulary in no namespace) loses every element a template builds: the
generated _dispatch passes foreign-namespace nodes through untouched,
but for those ODDs the template's <li> looks exactly like a source
element and falls through to "apply children", dropping the wrapper.
Source code in src/opm/runtime/pm_runtime.py
apply ¶
Transform nodes via dispatch(config, node, params).
Source code in src/opm/runtime/pm_runtime.py
apply_template_param_value ¶
Normalize and dispatch raw for pb:template [[param]] substitution.
XPath (or a literal . param) may yield the context element itself. Passing
that element through apply would re-dispatch the same TEI node and, in
templates, often stringifies it. When an item is source_node, recurse on
child_nodes(source_node) instead (same rule as apply_children).
Source code in src/opm/runtime/pm_runtime.py
inject_cached_footnotes ¶
Append the footnote bodies collected in config.state after the main flow.
HTML: HtmlOutputFunctions stores
dl.footnote elements. Markdown: stores reference-definition strings.
Source code in src/opm/runtime/pm_runtime.py
XPath extensions¶
opm.runtime.xpath_extensions ¶
Load Python callables as XPath 3.1 extension functions (tp: prefix).
expect_element ¶
Unwrap an XPath item and require an lxml element.
Extension functions receive XPath values; node items can arrive as
XPathNode wrappers.
Source code in src/opm/runtime/xpath_extensions.py
expect_string ¶
Normalize an XPath argument to a string.
Accepts atomics, single-item sequences, XPathNode wrappers, and elements. Elements are converted from their string value (concatenated descendant text).
Source code in src/opm/runtime/xpath_extensions.py
expect_text ¶
Like expect_string but intended for human-facing text.
Source code in src/opm/runtime/xpath_extensions.py
fingerprint_for_module ¶
Cache key fragment: import path plus source mtime when available.
Source code in src/opm/runtime/xpath_extensions.py
load_extension_callables ¶
Import module_dotted_path and collect public callables (name does not start with _).
Skips classes and non-routine callables so tp: functions map to plain functions/methods.
Source code in src/opm/runtime/xpath_extensions.py
build_extension_parser ¶
build_extension_parser(
default_element_ns: str,
callables: dict[str, Callable[..., Any]],
namespaces: dict[str, str] | None = None,
base_uri: str | None = None,
) -> OpmXPathParser
Create an OpmXPathParser with tp: external functions.
Source code in src/opm/runtime/xpath_extensions.py
Built-in XPath helper functions¶
opm.runtime.common_xpath_functions ¶
Common XPath extension functions shared across projects.
format_date ¶
Format a TEI-style xs:date value for display in a popover/title.
Source code in src/opm/runtime/common_xpath_functions.py
heading_number ¶
Port of pmf:heading-number from ext-common.xql (TEI div outline numbering).
Returns a dotted index such as 1.2.3: at each level, the 1-based index among
preceding tei:div siblings, joined from outer ancestor div down to div.
Source code in src/opm/runtime/common_xpath_functions.py
roman_fn ¶
XPath-accessible version of ec:roman-fn.
Takes an integer (1-based count) and returns a letter (a-z, no j), cycling through the alphabet using mod 25 arithmetic.
Example: 1 -> 'a', 25 -> 'z', 26 -> 'a', etc.
Source code in src/opm/runtime/common_xpath_functions.py
request ¶
tp:request(uri) — HTTP GET to uri; XML responses become element nodes.
Inspects the response Content-Type: XML media types (application/xml,
text/xml, or */*+xml) are parsed and returned as an elementpath node
so path expressions such as tp:request($uri)/entry work. All other types
are returned as a decoded string.
When the surrounding document uses a default element namespace (e.g. TEI),
unprefixed child steps on the fetched tree resolve in that namespace; for
namespace-less API XML use *[local-name()='entry'] instead of /entry.