Node#

Qualified name: rsm.nodes.Node

class rsm.nodes.Node(label='', types=None, number=None, nonum=False, reftext_template='')[source]#

Bases: None

A node in the manuscript tree.

A node represents a semantically meaningful element of the manuscript.

Parameters:
  • label (str) – Unique identifier for this node.

  • types (Optional[list[str]]) – Types of this node.

  • number (Optional[int]) – Node number.

  • nonum (bool) – Whether this node should be automatically given a number.

  • reftext_template (str) – If not empty, replaces classreftext.

See also

Manuscript

The class of the root node of a manuscript tree.

NodeWithChildren

Subclass that implements methods to handle children.

Notes

An instance of Node cannot have children. Only instances of (subclasses) of NodeWithChildren may have them. However, for the sake of having a uniform API, Node implements the property children, which always returns an empty tuple.

Methods

first_ancestor_of_type

First ancestor of the specified type.

first_of_type

First child of the specified type.

ingest_dict_as_meta

last_of_type

Last child of the specified type.

metakeys

The valid meta keys of the given class.

next_sibling

The next sibling, optionally of a specified type.

prev_sibling

The previous sibling, optionally of a specified type.

remove_self

Remove this Node from its parent's children.

replace_self

Replace this node in its parent's children.

sexp

Represent this node as an S expression.

traverse

Generate the descendents of this Node in depth-first order.

Attributes

autonumber

Whether to automatically assign a number to this node during transform step.

children

Tuple with this Node's children.

classreftext

full_number

newmetakeys

Meta keys to add to those of the parent class.

number_as

number_within

parent

The parent of this Node, or None.

possible_parents

Allowed types of parent Nodes.

reftext

label

Unique identifier.

types

Types of this node.

number

Node number.

nonum

Whether this node should be automatically given a number.

reftext_template

Reftext template, or "" to use classreftext.

autonumber: ClassVar[bool] = False[source]#

Whether to automatically assign a number to this node during transform step.

Examples

>>> msc, thm1, thm2 = nodes.Manuscript(), nodes.Theorem(), nodes.Theorem()
>>> (thm1.number, thm2.number) == (None, None)
True
>>> tform = rsm.transformer.Transformer()
>>> tform.transform(msc.append([thm1, thm2]))  
>>> thm1.number, thm2.number
(1, 2)
property children: tuple[source]#

Tuple with this Node’s children.

first_ancestor_of_type(cls)[source]#

First ancestor of the specified type.

Parameters:

cls (Union[Type[Node], tuple[Type[rsm.nodes.Node]]]) – Desired class of the ancestor.

Returns:

The first ancestor of the specified type, or None.

Return type:

Optional[Node]

Examples

Given the tree

>>> t = nodes.Text("Hello")
>>> p = nodes.Paragraph().append(nodes.Span().append(t))
>>> print(p.sexp())
(Paragraph
  (Span
    (Text)))

Find an ancestor of a desired type.

>>> t.first_ancestor_of_type(nodes.Paragraph)
Paragraph(parent=None, [Span])

Always check the return value against None.

>>> t.first_ancestor_of_type(nodes.Manuscript) is None
True
first_of_type(cls, return_idx=False)[source]#

First child of the specified type.

Parameters:
  • cls (Union[Type[Node], tuple[Type[rsm.nodes.Node]]]) – Desired class of the child.

  • return_idx (bool) – Whether to return the index of the child among this node’s children.

Returns:

  • Node – The first child of the specified type, or None.

  • Node, int – If return_idx is True, return (child, index), or (None, None).

Return type:

Union[Node, None, tuple[Optional[rsm.nodes.Node], Optional[int]]]

Examples

>>> p = nodes.Paragraph().append([nodes.Text("one"), nodes.Text("two")])
>>> p.first_of_type(nodes.Text)
Text("one")
>>> p.first_of_type(nodes.Text, return_idx=True)
(Text("one"), 0)

The index counts all existing children.

>>> p.prepend(nodes.Span())
Paragraph(parent=None, [Span, Text, Text])
>>> p.first_of_type(nodes.Text, return_idx=True)
(Text("one"), 1)
label: str[source]#

Unique identifier.

last_of_type(cls, return_idx=False)[source]#

Last child of the specified type.

For details, see first_of_type().

Examples

>>> p = nodes.Paragraph().append([nodes.Span(), nodes.Text("one"), nodes.Text("two")])
>>> p.last_of_type(nodes.Text, return_idx=True)
(Text("two"), 2)
classmethod metakeys()[source]#

The valid meta keys of the given class.

Returns:

The valid meta keys.

Return type:

set[str]

Examples

>>> all_nodes_meta = {"label", "types", "nonum", "reftext"}
>>> nodes.Node.metakeys() == all_nodes_meta
True
>>> nodes.Span.metakeys() - all_nodes_meta == {"strong", "emphas", "little", "insert", "delete"}
True
>>> nodes.Author.metakeys()  - all_nodes_meta == {"name", "affiliation", "email"}
True
newmetakeys: ClassVar[set] = {'label', 'nonum', 'reftext', 'types'}[source]#

Meta keys to add to those of the parent class.

Important

Only use this when defining a new Node subclass. When dealing with Node isntances, do not access this attribute directly neither for reading nor writing. Always use metakeys() in that case.

See also

metakeys()

Examples

The keys in newmetakeys are added to the meta keys of the parent class.

>>> nodes.Heading.newmetakeys
{'title'}
>>> nodes.Heading.metakeys() == nodes.Node.metakeys() | {"title"}
True

The intended use, and only supported use, of newmetakeys is at the time of class definition.

>>> class NewNode(nodes.Node):
...     newmetakeys = {"newkey"}
>>> NewNode.metakeys() == nodes.Node.metakeys() | {"newkey"}
True
next_sibling(cls=None)[source]#

The next sibling, optionally of a specified type.

For details, see prev_sibling().

See also

prev_sibling()

nonum: bool[source]#

Whether this node should be automatically given a number.

number: int[source]#

Node number.

property parent: Optional[NodeWithChildren][source]#

The parent of this Node, or None.

possible_parents: ClassVar[set[Type[rsm.nodes.NodeWithChildren]]] = {}[source]#

Allowed types of parent Nodes.

When setting the parent of a Node, this attribute is checked to see whether the intended parent has an admissible type.

Examples

This is a class variable

>>> nodes.Item.possible_parents == {nodes.Itemize, nodes.Enumerate, nodes.Contents}
True

This variable is checked when setting the parent directly.

>>> it = nodes.Item()
>>> it.parent = nodes.Paragraph()
Traceback (most recent call last):
rsm.nodes.RSMNodeError: Node of type <class 'rsm.nodes.Item'> cannot have parent of type <class 'rsm.nodes.Paragraph'>

This is also used when setting the parent in some other indirect way, for example when calling append() on the desired parent.

>>> nodes.Paragraph().append(it)
Traceback (most recent call last):
rsm.nodes.RSMNodeError: Node of type <class 'rsm.nodes.Item'> cannot have parent of type <class 'rsm.nodes.Paragraph'>

Allowed parents proceed without raising.

>>> nodes.Itemize().append(it)
Itemize(parent=None, [Item])
prev_sibling(cls=None)[source]#

The previous sibling, optionally of a specified type.

Parameters:

cls (Optional[Union[Type[Node], str]]) – The type of the desired sibling. If "self", search for the previous sibling with the same type as this node. If None, return the immediately preceding sibling, regardless of its type.

Returns:

The desired sibling, or None.

Return type:

Optional[Node]

Examples

>>> p, s, t1, t2 = nodes.Paragraph(), nodes.Span(), nodes.Text("one"), nodes.Text("two")
>>> p.append([t1, s, t2])  
>>> t2.prev_sibling()
Span(parent=Paragraph)
>>> t2.prev_sibling(nodes.Text)
Text("one")
>>> t1.prev_sibling() is None
True

Use "self" to find nodes of the same type.

>>> s2 = nodes.Span()
>>> p.append(s2)
Paragraph(parent=None, [Text, Span, Text, Span])
>>> s2.prev_sibling() is t2
True
>>> s2.prev_sibling("self") is s
True
reftext_template: str[source]#

Reftext template, or “” to use classreftext.

remove_self()[source]#

Remove this Node from its parent’s children.

See also

replace_self()

Examples

>>> t = nodes.Text("remove me")
>>> p = nodes.Paragraph().append(t)
>>> p.children
(Text("remove me"),)
>>> t.remove_self()
>>> p.children
()
replace_self(replacement)[source]#

Replace this node in its parent’s children.

This is mostly useful during the transform step.

Parameters:

replacement (Union[Node, Iterable[Node]]) – The Node or Nodes to replace this with.

Raises:

RSMNodeError – If this Node’s parent is None.

See also

remove_self()

Examples

Wrap a Text in a strong Span to render it in bold face.

>>> p, t = nodes.Paragraph(), nodes.Text("one")
>>> p.append(t)         
>>> print(p.sexp())
(Paragraph
  (Text))
>>> s = nodes.Span(strong=True)
>>> t.replace_self(s)
>>> s.append(t)         
>>> print(p.sexp())
(Paragraph
  (Span
    (Text)))

Note the call to replace_self() must happen before the Text is added to the Span.

May also replace with a list of Nodes.

>>> t.replace_self([nodes.Text("new one"), nodes.Text("two")])
>>> print(p.sexp())
(Paragraph
  (Span
    (Text)
    (Text)))

The following recipe uses the above example to wrap every Text within root in a strong Span. Note this is done to each Text descendent of root, regardless of depth, and without any reference to their original immediate parents.

>>> root = nodes.Manuscript().append(...)    
>>> for t in root.traverse(nodeclass=Text):  
...     s = nodes.Span(strong=True)
...     t.replace_self(s)
...     s.append(t)
sexp(tab_width=2, meta=False, ignore_meta_keys=None)[source]#

Represent this node as an S expression.

Parameters:
  • tab_width (int) – How many spaces of indentation at each depth level.

  • meta (bool) – Whether to include meta in the output.

  • ignore_meta_keys (Optional[set]) – When meta is True, this controls which meta keys to include.

Returns:

A string representation of the Node and its descendents.

Return type:

str

See also

traverse()

Examples

>>> p1, p2, p3, p4 = [nodes.Paragraph().append(nodes.Text()) for _ in range(4)]
>>> msc = nodes.Manuscript().append(
...     [
...         nodes.Section().append(p1),
...         nodes.Section().append([nodes.Subsection().append([p2, p3])]),
...         nodes.Section().append(p4),
...     ]
... )

By default, meta keys are not included in the output.

>>> print(msc.sexp())
(Manuscript
  (Section
    (Paragraph
      (Text)))
  (Section
    (Subsection
      (Paragraph
        (Text))
      (Paragraph
        (Text))))
  (Section
    (Paragraph
      (Text))))

sexp() is useful when transforming the tree.

>>> p3.replace_self(nodes.Paragraph().append(nodes.Span(strong=True).append(nodes.Text())))
>>> print(msc.sexp())
(Manuscript
  (Section
    (Paragraph
      (Text)))
  (Section
    (Subsection
      (Paragraph
        (Text))
      (Paragraph
        (Span
          (Text)))))
  (Section
    (Paragraph
      (Text))))

Output meta for even more debugging information.

>>> print(msc.sexp(meta=True))
(Manuscript { :reftext: Manuscript  }
  (Section { :reftext: Section  }
    (Paragraph { :reftext: Paragraph  }
      (Text { :reftext: Text  })))
  (Section { :reftext: Section  }
    (Subsection { :reftext: Section  }
      (Paragraph { :reftext: Paragraph  }
        (Text { :reftext: Text  }))
      (Paragraph { :reftext: Paragraph  }
        (Span { :reftext: Span , :strong: True }
          (Text { :reftext: Text  })))))
  (Section { :reftext: Section  }
    (Paragraph { :reftext: Paragraph  }
      (Text { :reftext: Text  }))))

Use ignore_meta_keys for a less verbose output.

>>> print(msc.sexp(meta=True, ignore_meta_keys={"reftext"}))
(Manuscript {  }
  (Section {  }
    (Paragraph {  }
      (Text {  })))
  (Section {  }
    (Subsection {  }
      (Paragraph {  }
        (Text {  }))
      (Paragraph {  }
        (Span { :strong: True }
          (Text {  })))))
  (Section {  }
    (Paragraph {  }
      (Text {  }))))
traverse(*, condition=None, nodeclass=None)[source]#

Generate the descendents of this Node in depth-first order.

By default, yield this node and then every descendent in depth-first order. If condition is given, yield only those Nodes that satisfy the condition. If nodeclass is given, it overrides condition and only those descendents of the specified type are yielded.

Parameters:
  • condition (Optional[Callable[[Node], bool]]) – Callable that receives a single argument, a descendent Node, and returns whether it should be yielded.

  • nodeclass (Optional[NodeSubType]) – A Node subclass of the desired yielded descendent Nodes. If not None, condition is ignored.

Yields:

Node

See also

sexp()

Notes

Passing nodeclass=<NodeSubType> is equivalent to passing condition=lambda n: isinstance(n, <NodeSubType>).

Examples

>>> p1, p2, p3, p4 = [nodes.Paragraph().append(nodes.Text()) for _ in range(4)]
>>> msc = nodes.Manuscript().append(
...     [
...         nodes.Section().append(p1),
...         nodes.Section().append([nodes.Subsection().append([p2, p3])]),
...         nodes.Section().append(p4),
...     ]
... )

Visit every descendent, including self.

>>> for n in msc.traverse(): print(n)
Manuscript(parent=None, [Section, Section, Section])
Section(parent=Manuscript, [Paragraph])
Paragraph(parent=Section, [Text])
Text("")
Section(parent=Manuscript, [Subsection])
Subsection(parent=Section, [Paragraph, Paragraph])
Paragraph(parent=Subsection, [Text])
Text("")
Paragraph(parent=Subsection, [Text])
Text("")
Section(parent=Manuscript, [Paragraph])
Paragraph(parent=Section, [Text])
Text("")

Use nodeclass to yield only nodes of a specified type. Note that subclasses are also yielded.

>>> for n in msc.traverse(nodeclass=nodes.Section): print(n)
Section(parent=Manuscript, [Paragraph])
Section(parent=Manuscript, [Subsection])
Subsection(parent=Section, [Paragraph, Paragraph])
Section(parent=Manuscript, [Paragraph])

Yield only nodes satisfying an arbitrary condition

>>> msc.children[1].nonum = True
>>> for n in msc.traverse(condition=lambda n: n.nonum): print(n)
Section(nonum=True, parent=Manuscript, [Subsection])
types: list[str][source]#

Types of this node.