Skip to content

Validation and utilities

Validate a tree

marktripy.core.validator checks that a tree has a sensible structure. It is useful after you build or edit a tree by hand, before you render it.

from marktripy.core.ast import Document, List, Paragraph, Text
from marktripy.core.validator import validate_ast

doc = Document()
doc.add_child(Text("loose"))          # inline node directly under the document
bad_list = List()
bad_list.add_child(Paragraph())       # list that contains a paragraph
doc.add_child(bad_list)

for error in validate_ast(doc):
    print(error)

Output:

Document contains inline element at text node
List contains non-list-item: paragraph at paragraph node

Each error is a ValidationError (a subclass of Exception). Its message ends with at <type> node, and its node attribute holds the node that failed.

Entry points

Call Result
validate_ast(ast, strict=False) Returns a list of ValidationError; empty if the tree is valid.
validate_ast_strict(ast) Runs strict validation and raises the first error.
ASTValidator(strict=False).validate(ast) Same as validate_ast. The errors are also kept in validator.errors.
ASTValidator(strict=…).validate_strict(ast) Validates and raises the first error, if any. Despite the name, it uses the validator's own strict setting.

Rules

The validator visits every node and calls _validate_<type> for it.

Node type Always checked Checked only with strict=True
document No direct text, emphasis, strong or inline_code children —
heading Level is 1–6; has children or content —
list Only list_item children; ordered start is not negative —
list_item — Not empty
link href is not empty Has text
image src is not empty; has no children —
table Only table_row children; all rows have the same number of cells —
table_row Only table_cell children —
blockquote — Not empty
any other type — Logs a warning (not an error)

Example: a link with an empty target, as parsed from [x]():

from marktripy.core.parser import ParserRegistry
from marktripy.parsers import markdown_it

doc = ParserRegistry.create("markdown-it").parse("# ok\n\n[x]()\n")
print(validate_ast(doc))
# [ValidationError('Link has no href at link node')]

Slugs and IDs

marktripy.utils.slugify contains the text helpers the transformers use. You can call them directly.

slugify(text, lowercase=True, separator="-", max_length=None, allowed_chars=None)

Converts text to a URL-friendly slug in five steps: NFKD-normalise and drop non-ASCII, optionally lower-case, remove characters other than letters, digits, whitespace, hyphens and allowed_chars, collapse whitespace and hyphen runs into separator, strip separator from the ends. If max_length is set, the result is cut to that length and trailing separators are removed.

from marktripy.utils.slugify import slugify

slugify("Hello, World! Café")             # 'hello-world-cafe'
slugify("Hello World", separator="_")     # 'hello_world'
slugify("v1.2 notes", allowed_chars=".")  # 'v1.2-notes'

generate_id(text, existing_ids=None)

Slugifies text and makes it unique against the set existing_ids by appending -1, -2, …. An empty slug becomes section. The function does not add the result to the set.

from marktripy.utils.slugify import generate_id

generate_id("Intro", {"intro"})   # 'intro-1'
generate_id("!!!")                # 'section'

IDGenerator(prefix="", separator="-")

A stateful wrapper around generate_id. generate(text) prepends prefix + separator when a prefix is set, returns a unique ID and remembers it in used_ids. has_id(value) checks whether an ID was issued; reset() forgets them all.

extract_text(node)

Returns the plain text of a node: its content if set, otherwise the concatenated text of its children. Formatting is dropped, so **bold** text gives bold text.