Share a query across templates

A template's xpath-functions reference loads a separate named function library. CEM-QL passes declared scalars or explicitly selected XML nodes to XPath; ordinary slice bindings and CEMT rules keep the page reactive. Each XPath case has an adjacent CEM-QL pair with the same input and result. Edit either independently to compare them.

The templates share the compiled library. Its URL resolves from the declaration document, and each input keeps its own state and focus. Libraries are limited to 32 KiB and 64 functions; library imports are not supported yet. Each call also bounds text to 1 MiB per intermediate or final value and shares a finite work budget across its nested expressions.

For XML, cem-data @projection=xpath exposes the native document to the library. The reader reuses unchanged XML across renders, retains up to 16 documents per compiled template, and reports invalid input without a partial result. Each XML source is limited to 32 KiB, 64 levels and 4096 events.

Read the CEM-QL function use cases for string composition, predicates, native node queries, and string import and source metadata.

See also

XPath XML tables and trees · DOM merge and live slices · CEM-QL string functions

CEM-QL function use cases

Compose a label from scalar values

str:concat((text, " 🍒"), "") joins a sequence with an empty separator. It matches the library's $text || ' 🍒' for these string inputs, including an empty string and Unicode emoji. Use a nonempty separator when joining labels or tokens. This native CEM-QL call needs no XPath library dependency.

Extract a URL segment with a string chain

url.split("/").nth(6) selects the Pokémon ID from https://pokeapi.co/api/v2/pokemon/1/. Both splitting and zero-based selection follow Rust conventions. Out-of-range selection is empty; use ?? "No ID" for a fallback. Try the interactive string-function samples or the JSON cell override.

Choose presentation with a predicate

node == "cherry" matches the library's string equality test. A CEMT rule consumes either boolean result; the lower-priority fallback handles other text. Use seq:any(items, fn(item) => condition) for an existential test over a sequence.

Select native nodes and inspect attributes

Explore the DOM chain samples for fluent navigation, predicates, extraction and immutable sorting.

seq:where(nodes, fn(n) => n.kind == "element" && n.name == "item" && n.namespace == "") selects unqualified item elements. Apply it at each level to match /r/item. Filtering by kind and namespace avoids treating comments or namespaced elements as matching data.

The quantity examples expect numeric qty values; a missing quantity selects low stock. dom:attribute(node, "qty") selects only the unqualified attribute, like XPath @qty. Use dom:attribute(node, {namespace: "urn:catalog", name: "qty"}) for a qualified attribute; prefixes and wildcards are not selectors. Both forms retain the native attribute and its typed values. num:double converts both the quantity and threshold to the same numeric type for CEM-QL comparison. dom:text(item) extracts descendant text, CDATA and whitespace nodes, excluding comments and processing instructions. All selected nodes retain their CEM owner and source maps. XML, JSON, YAML and CSV document syntax is decoded only by CEM-ML import.

For a search at any depth, dom:descendants(node) is the CEM-QL pair of XPath descendant::node(). It excludes the starting node and returns children in depth-first source order. Filter by kind, namespace and name for descendant::item. The pipeline nodes.dom:descendants() preserves input order and duplicates. Attributes and reference targets are separate from children; use dom:attribute and .targets explicitly. Unsupported node views and denied access raise errors.

Parse a string and keep source metadata

data:parse(source, "xml") returns a native CEM document directly. The format may also be json, yaml or csv. JSON uses the same native XML-shaped projection as XPath json-to-xml, without constructing or reparsing XML text. An optional CEM-QL options record supports JSON {duplicates: "use-first", escape: true}, CSV {header: "present"}, and an explicit {"base-uri": "https://example.test/source"}.

data:node_key(node) and data:line_number(node) expose selection provenance. data:base_uri(node) and data:document_uri(node) read retained metadata; parsed strings have no document URI. Import is limited to 32 KiB, 64 levels and 4096 values/events. Each parse produces a new owner; stable source keys, rather than owner identity, track selection.

Use try { data:parse(source, "xml") } catch (code, message) { message } to recover from malformed input. Query diagnostics preserve the import error category and parser locations. Import limits, cancellation and unsupported capabilities remain fatal. data:read and cem-data, used by the paired XML examples, keep their cached report contract with error and root; their strict input profile is unchanged.

Group, sort and normalize viewer values

seq:group_by(rows, fn(row) => row.name) returns groups with key and native items. Pass those values explicitly to templates in place of XPath's ambient group context. CEM grouping accepts one atomic key and uses CEM identity equality; it does not implement every XSLT grouping mode.

seq:sorted(rows, fn(row) => row.attributes.value, "ascending", "number") performs a stable numeric sort with missing/invalid keys last. Supply a selector for the particular attribute when a row has several. XPath sorting uses its own typed comparison rules; the viewer authors the same missing-last policy.

str:trim(text, "xml") and str:normalize_space(text, "xml") recognize only space, tab, CR and LF, matching the viewer's XPath whitespace expressions. Nonbreaking spaces remain text. Omit the profile to retain the original CEM-QL whitespace behavior. str:replace remains literal replacement; it is not XPath's regular-expression function.