transform¶
The high-level transform API. Import these to run a compiled transform module against parsed XML.
opm.transform ¶
Core transform API — import this to drive transforms from your own Python scripts.
Most callers want opm.Project.transform, which also compiles the
ODD and keeps the loaded module and registers between calls. The functions
here are the layer below it, at increasing levels of abstraction.
run_transform(mod, element, ...) is the lowest
level. The caller supplies an already-loaded module and an already-selected
lxml element; the function serializes and optionally wraps the result in the
Jinja2 document template:
from opm.odd_cache import ensure_compiled_module
from opm.resources import packaged_odd
from opm.transform import load_transform_module, run_transform
from lxml import etree
path, _ = ensure_compiled_module(packaged_odd('teipublisher'))
mod = load_transform_module(path)
root = etree.parse('document.xml').getroot()
html = run_transform(mod, root) # full document
fragment = run_transform(mod, root.find('.//{*}div')) # single element
transform_node(script_path, root, *, xpath=None, ...)
is the mid level. It loads the module from script_path and, if xpath is
given, selects the target element before transforming:
from opm.odd_cache import ensure_compiled_module
from opm.resources import packaged_odd
from opm.transform import transform_node
from lxml import etree
path, _ = ensure_compiled_module(packaged_odd('teipublisher'))
root = etree.parse('document.xml').getroot()
html = transform_node(path, root, xpath='//body/div[1]')
transform_file(module, xml_path, *, xpath=None, ...)
is the highest level, and what opm transform runs. It also parses the XML
file and takes everything else from opm.toml: parameters, registers,
extensions, web components, template:
from opm.odd_cache import ensure_compiled_module
from opm.resources import packaged_odd
from opm.transform import transform_file
path, _ = ensure_compiled_module(packaged_odd('teipublisher'))
html = transform_file(path, Path('document.xml'), xpath='//body/div[1]')
xpath_select(root, expr, ...) evaluates XPath
against a parsed document without any namespace bookkeeping: unprefixed names
automatically match the document's namespace:
from opm.transform import xpath_select
from lxml import etree
root = etree.parse('document.xml').getroot()
chapters = xpath_select(root, '//body/div')
TemplateArguments ¶
template_arguments ¶
template_arguments(
mode: OutputMode,
config: ProjectConfig,
override: Path | None = None,
) -> TemplateArguments
The template argument run_transform takes for a run in mode.
override (--template) wins over the project's
[transform.<type>] template. Without either, an HTML or Typst shell
falls back to its packaged default inside run_transform, and DOCX
to the packaged Word style template. Modes that take no template get none.
Source code in src/opm/transform.py
load_transform_module ¶
Load a .py file that defines transform() and OUTPUT_MODE.
Source code in src/opm/transform.py
xpath_select ¶
xpath_select(
root: _Element,
expr: str,
params: dict[str, str] | None = None,
xpath_extensions: Sequence[str] | None = None,
xpath_base_uri: str | None = None,
xpath_documents: dict[str, Any] | None = None,
xpath_collections: dict[str, list] | None = None,
xpath_variables: dict[str, Any] | None = None,
xpath_namespaces: dict[str, str] | None = None,
*,
xpath_env: XPathEnvironment | None = None,
) -> list
Evaluate XPath 3.1 expr against root, returning a plain list.
The document's default namespace URI (taken from root's nsmap) is set
as the XPath default element namespace, so unprefixed element names match
without any prefix mapping:
chapters = xpath_select(root, '//body/div') # TEI, DocBook, …
titles = xpath_select(root, '//div/head/string()') # atomic results
Element results are returned as lxml _Element objects.
Atomic expressions (count(…), string(…)) return the corresponding
Python scalar.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
root
|
_Element
|
Document root element; its namespace determines the default element namespace. |
required |
expr
|
str
|
XPath 3.1 expression with unprefixed element names. |
required |
params
|
dict[str, str] | None
|
Values bound as the XPath |
None
|
xpath_extensions
|
Sequence[str] | None
|
Dotted module paths for custom XPath functions in the |
None
|
xpath_env
|
XPathEnvironment | None
|
Evaluate in this environment instead of one built from the other arguments; its cached document trees are then reused. |
None
|
Source code in src/opm/transform.py
load_xpath_documents ¶
Parse configured XPath documents keyed by their absolute file URI.
Each tree is wrapped in its elementpath node tree once, here, rather than
left as a bare _ElementTree. XPathContext.__init__ runs
get_node_tree() over every entry of documents on each
construction, and for a bare lxml tree that means a full
build_lxml_node_tree() walk every time — with a context built per
doc()-using predicate, the register documents were being re-wrapped
thousands of times per chunked file. get_node_tree() short-circuits on
an already-wrapped DocumentNode, so pre-wrapping turns that back into a
dict lookup. Mirrors what wrapped
does for the main document.
Source code in src/opm/transform.py
load_xpath_collections ¶
load_xpath_collections(
collections: Sequence[CollectionConfig],
documents: dict[str, Any] | None = None,
) -> tuple[dict[str, list], dict[str, Any]]
Return (collections, documents) maps for the XPath dynamic context.
Each member document is parsed and wrapped once (see
load_xpath_documents) and registered in both returned maps. The
second registration is not redundant: fn:id resolves its target document
through XPathContext.get_root(), which searches root and
documents but never collections. Without it,
collection($uri)/id($key) returns the empty sequence — silently, with no
error — which is the shape most register lookups take.
documents is merged into (and takes precedence in) the returned document
map, so a file listed both in [transform] documents and in a collection
is parsed once and shared as the same node object.
Source code in src/opm/transform.py
load_project_documents ¶
load_project_documents(
config: ProjectConfig,
) -> tuple[dict[str, Any], dict[str, list]]
(documents, collections) for the XPath dynamic context, from config.
Parses [transform] documents and every [[transform.collections]]
member once. Pass the result to project_xpath_env to share the
parsed registers across several documents.
Source code in src/opm/transform.py
project_xpath_env ¶
project_xpath_env(
config: ProjectConfig,
xml_path: Path | None = None,
*,
extensions: Sequence[str] | None = None,
documents: tuple[dict[str, Any], dict[str, list]]
| None = None,
) -> XPathEnvironment
The XPath environment a run over xml_path evaluates in.
Registers, collections, variables, namespaces and extension modules all
come from config; extensions replaces the configured modules when
given. xml_path is the base URI doc() resolves against. documents
is a load_project_documents result to reuse instead of parsing the
registers again.
The config's [project] pythonpath goes on sys.path first, so its
extension modules import from the Python API as they do from the CLI.
Source code in src/opm/transform.py
run_transform ¶
run_transform(
mod: ModuleType,
root: _Element,
*,
parameters: dict[str, str] | None = None,
webcomponents: bool = False,
apply_template: bool = True,
template_path: Path | None = None,
template_context: dict[str, Any] | None = None,
docx_template: Path | None = None,
typst_template_path: Path | None = None,
epub_chunking: ChunkingConfig | None = None,
epub_css: Path | None = None,
epub_skip_title: bool = False,
xpath_env: XPathEnvironment | None = None,
) -> str | bytes
Run mod against root and return the serialized output.
For HTML output, if the result contains a full <html> document and
apply_template is True, the result is wrapped in the Jinja2 document
template. Fragment transforms (e.g. a single <div>) skip this step.
For DOCX / EPUB output, returns raw bytes (the package file content).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mod
|
ModuleType
|
A loaded transform module (from |
required |
root
|
_Element
|
The lxml element to transform. |
required |
parameters
|
dict[str, str] | None
|
XPath |
None
|
webcomponents
|
bool
|
Enable TEI Publisher web-component mode. |
False
|
apply_template
|
bool
|
Wrap full-document HTML output in the Jinja2 template. |
True
|
template_path
|
Path | None
|
Override Jinja2 template (default: packaged template). |
None
|
template_context
|
dict[str, Any] | None
|
Project |
None
|
docx_template
|
Path | None
|
Path to a |
None
|
typst_template_path
|
Path | None
|
Jinja2 template for Typst document shell. |
None
|
epub_chunking
|
ChunkingConfig | None
|
Chapter selection for |
None
|
epub_css
|
Path | None
|
Stylesheet appended last to the EPUB package. |
None
|
epub_skip_title
|
bool
|
Omit the generated EPUB title page. |
False
|
xpath_env
|
XPathEnvironment | None
|
The XPath environment to evaluate in (see
|
None
|
Source code in src/opm/transform.py
326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 | |
transform_node ¶
transform_node(
script_path: Path | ModuleType,
root: _Element,
*,
xpath: str | None = None,
parameters: dict[str, str] | None = None,
webcomponents: bool = False,
apply_template: bool = True,
template_path: Path | None = None,
template_context: dict[str, Any] | None = None,
docx_template: Path | None = None,
typst_template_path: Path | None = None,
epub_chunking: ChunkingConfig | None = None,
epub_css: Path | None = None,
epub_skip_title: bool = False,
xpath_env: XPathEnvironment | None = None,
) -> str | bytes
Load script_path as a transform module and apply it to root.
If xpath is given it is evaluated against root via
resolve_element to select the
actual element to transform; unprefixed names use the document's default
namespace. Without xpath, root itself is the transform target.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
script_path
|
Path | ModuleType
|
Path to the compiled transform |
required |
root
|
_Element
|
The lxml element that acts as the document root for XPath
evaluation and (when xpath is |
required |
xpath
|
str | None
|
XPath 3.1 expression selecting a single child element to transform instead of root. |
None
|
parameters
|
dict[str, str] | None
|
XPath |
None
|
webcomponents
|
bool
|
Enable TEI Publisher web-component mode. |
False
|
apply_template
|
bool
|
Wrap full-document HTML output in the Jinja2 template. |
True
|
template_path
|
Path | None
|
Override Jinja2 template (default: packaged template). |
None
|
template_context
|
dict[str, Any] | None
|
Project |
None
|
xpath_env
|
XPathEnvironment | None
|
The XPath environment to evaluate in (see
|
None
|
Source code in src/opm/transform.py
transform_file ¶
transform_file(
module: Path | ModuleType,
xml_path: Path,
*,
xpath: str | None = None,
parameters: dict[str, str] | None = None,
xpath_extensions: Sequence[str] | None = None,
webcomponents: bool | None = None,
template: Path | None = None,
config: ProjectConfig | None = None,
documents: tuple[dict[str, Any], dict[str, list]]
| None = None,
) -> str | bytes
Transform xml_path (or an XPath-selected element within it) with the project's settings.
This is what opm transform runs. Everything the arguments leave open
comes from the project config: $parameters, registers and collections,
XPath variables and extensions, web components, the template and its
context, and the EPUB chapter selection.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
module
|
Path | ModuleType
|
The compiled transform module, or the path to its |
required |
xml_path
|
Path
|
Path to the XML input file. |
required |
xpath
|
str | None
|
XPath 3.1 expression selecting the transform root element. Unprefixed names use the document's default namespace. |
None
|
parameters
|
dict[str, str] | None
|
XPath |
None
|
xpath_extensions
|
Sequence[str] | None
|
Dotted module paths for custom XPath functions.
|
None
|
webcomponents
|
bool | None
|
Enable web-component mode.
|
None
|
template
|
Path | None
|
Template override (see |
None
|
config
|
ProjectConfig | None
|
Pre-loaded |
None
|
documents
|
tuple[dict[str, Any], dict[str, list]] | None
|
A |
None
|
Returns str for text output modes (HTML, Markdown, Typst) and bytes
for binary ones (DOCX, EPUB). To get a PDF, pass Typst output to
opm.typst_compile.compile_pdf.
Source code in src/opm/transform.py
transform_with_config ¶
transform_with_config(
mod: ModuleType,
root: _Element,
xml_path: Path | None,
config: ProjectConfig,
*,
xpath: str | None = None,
parameters: dict[str, str] | None = None,
xpath_extensions: Sequence[str] | None = None,
webcomponents: bool | None = None,
template: Path | None = None,
documents: tuple[dict[str, Any], dict[str, list]]
| None = None,
) -> str | bytes
Transform the already parsed root with config's settings.
The part of transform_file after parsing, shared with
opm.project.Project.transform. xml_path is the file root was
read from, if any: doc() resolves against it and it becomes
$parameters?input_path.