Skip to content

Commit

Permalink
Reorder consecutive index and target nodes
Browse files Browse the repository at this point in the history
An index directive creates a index node followed by a target node.  Two
consecutive index directives::

.. index:: first
.. index:: second

create index, target, index, target, i.e. a mixture.  The interspersed
index nodes prevent other transformations, e.g. PropagateTargets to
properly work on the target nodes.

Apply a transformation which reorders mixed and consecutive index and
target nodes such that first all index nodes are before all target
nodes:

Given the following document as input:

    <document>
        <target ids="id1" ...>
        <index entries="...1...">
        <target ids="id2" ...>
        <target ids="id3" ...>
        <index entries="...2...">
        <target ids="id4" ...>

The transformed result will be:

    <document>
        <index entries="...1...">
        <index entries="...2...">
        <target ids="id1" ...>
        <target ids="id2" ...>
        <target ids="id3" ...>
        <target ids="id4" ...>
  • Loading branch information
latosha-maltba committed May 29, 2022
1 parent f93482a commit 9cd1f4c
Showing 1 changed file with 77 additions and 1 deletion.
78 changes: 77 additions & 1 deletion sphinx/transforms/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import re
import unicodedata
import warnings
from typing import TYPE_CHECKING, Any, Dict, Generator, List, Optional, Tuple, cast
from typing import TYPE_CHECKING, Any, Dict, Generator, List, Optional, Tuple, Union, cast

import docutils
from docutils import nodes
Expand Down Expand Up @@ -417,6 +417,81 @@ def apply(self, **kwargs: Any) -> None:
)


class ReorderConsecutiveTargetAndIndexNodes(SphinxTransform):
"""Index nodes interspersed between target nodes prevent other
Transformations from combining those target nodes,
e.g. ``PropagateTargets``. This transformation reorders them:
Given the following ``document`` as input::
<document>
<target ids="id1" ...>
<index entries="...1...">
<target ids="id2" ...>
<target ids="id3" ...>
<index entries="...2...">
<target ids="id4" ...>
The transformed result will be::
<document>
<index entries="...1...">
<index entries="...2...">
<target ids="id1" ...>
<target ids="id2" ...>
<target ids="id3" ...>
<target ids="id4" ...>
"""

# Priority must smaller than the one of PropagateTargets transform, which
# has 260.
default_priority = 250

def apply(self, **kwargs: Any) -> None:
for target in self.document.findall(nodes.target):
self.reorder_around(target)

def reorder_around(self, start_node: nodes.Node) -> None:
# collect all follow up target or index sibling nodes (including node
# itself). Note that we cannot use the 'condition' to filter for index
# and target as we want *consecutive* target/index nodes.
nodes_to_reorder: List[Union[nodes.target, addnodes.index]] = []
node : nodes.Node
for node in start_node.findall(condition=None,
include_self=True,
descend=False,
siblings=True):
if not isinstance(node, nodes.target) and \
not isinstance(node, addnodes.index):
break # consecutive strike is broken
nodes_to_reorder.append(node)

if len(nodes_to_reorder) < 2:
return # Nothing to reorder

# Since we have at least two siblings, their parent is not None and
# supports children (e.g. is not Text)

parent_node: nodes.Element = nodes_to_reorder[0].parent
assert parent_node == nodes_to_reorder[-1].parent
first_idx = parent_node.index(nodes_to_reorder[0])
last_idx = parent_node.index(nodes_to_reorder[-1])
assert first_idx + len(nodes_to_reorder) - 1 == last_idx

def sortkey(node: nodes.Node) -> int:
if isinstance(node, addnodes.index):
return 1
elif isinstance(node, nodes.target):
return 2
else:
raise Exception('This cannot happen! (Unreachable code reached)')
# Important: The sort algorithm used must be a stable sort.
nodes_to_reorder.sort(key = sortkey)

# '+1' since slices are excluding the right hand index
parent_node[first_idx:last_idx + 1] = nodes_to_reorder


def setup(app: "Sphinx") -> Dict[str, Any]:
app.add_transform(ApplySourceWorkaround)
app.add_transform(ExtraTranslatableNodes)
Expand All @@ -433,6 +508,7 @@ def setup(app: "Sphinx") -> Dict[str, Any]:
app.add_transform(DoctreeReadEvent)
app.add_transform(ManpageLink)
app.add_transform(GlossarySorter)
app.add_transform(ReorderConsecutiveTargetAndIndexNodes)

return {
'version': 'builtin',
Expand Down

0 comments on commit 9cd1f4c

Please sign in to comment.