Skip to content

Commit a36b331

Browse files
committed
translator: delayed anchor injection for other refids
Expands the populated anchor set to include anchors for other reference identifier not processed. To support these anchor points, we populate known anchors to be added at a point of time where they can be added into the page. If we do not, it will generate anchors in locations where Confluence will render newlines. For example, adding an anchor before a paragraph, Confluence styling will ensure the paragraph starts on a new block leaving a less than ideal rendering. Instead, we populate anchors as we process and then jump them into the first block we can. Signed-off-by: James Knight <git@jdknight.me>
1 parent 40d54a6 commit a36b331

File tree

1 file changed

+37
-1
lines changed

1 file changed

+37
-1
lines changed

sphinxcontrib/confluencebuilder/storage/translator.py

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,13 +110,15 @@ def __init__(self, document, builder):
110110
self.numfig_format = config.numfig_format
111111
self.secnumber_suffix = config.confluence_secnumber_suffix
112112
self.todo_include_todos = getattr(config, 'todo_include_todos', None)
113+
self._anchor_cache = set()
113114
self._auto_context = []
114115
self._building_footnotes = False
115116
self._figure_context = []
116117
self._indent_level = 0
117118
self._list_context = ['']
118119
self._manpage_url = getattr(config, 'manpages_url', None)
119120
self._needs_navnode_spacing = False
121+
self._pending_anchors = []
120122
self._reference_context = []
121123
self._thead_context = []
122124
self._v2_header_added = False
@@ -267,6 +269,7 @@ def visit_title(self, node):
267269
new_targets = []
268270

269271
self.body.append(self.start_tag(node, f'h{self._title_level}'))
272+
self._delayed_anchor_inject(node)
270273

271274
if self.builder.name == 'singleconfluence':
272275
docname = self._docnames[-1]
@@ -1502,9 +1505,14 @@ def visit_target(self, node):
15021505
# sections which will have automatically created targets), we will
15031506
# build an anchor link for them; example cases include documentation
15041507
# which generate a custom anchor link inside a paragraph
1505-
if 'ids' in node and 'refuri' not in node:
1508+
if node.get('ids') and 'refuri' not in node:
15061509
self._build_id_anchors(node)
15071510
self.body.append(self.encode(node.astext()))
1511+
# if this target has a reference id, treat as a generic anchor
1512+
elif node.get('refid'):
1513+
# note: we do not add generic anchors immediately to avoid
1514+
# newline issues
1515+
self._pending_anchors.append(node.get('refid'))
15081516

15091517
raise nodes.SkipNode
15101518

@@ -3430,6 +3438,7 @@ def visit_line_block(self, node):
34303438
attribs['style'] = style
34313439

34323440
self.body.append(self.start_tag(node, tag, **attribs))
3441+
self._delayed_anchor_inject(node)
34333442
self.context.append(self.end_tag(node))
34343443

34353444
def depart_line_block(self, node):
@@ -3498,6 +3507,9 @@ def _build_id_anchors(self, node):
34983507
for id_ in node['ids']:
34993508
self._build_anchor(node, id_)
35003509

3510+
# also, include any pending anchors not added
3511+
self._delayed_anchor_inject(node)
3512+
35013513
def _build_anchor(self, node, anchor, *, force_compat=False):
35023514
"""
35033515
build an anchor on a page
@@ -3518,6 +3530,12 @@ def _build_anchor(self, node, anchor, *, force_compat=False):
35183530
force_compat (optional): always force compat anchor
35193531
"""
35203532

3533+
# ignore any duplicate anchors on the same page
3534+
if anchor in self._anchor_cache:
3535+
self.verbose(f'duplicate anchor ({self.docname}): {anchor}')
3536+
return
3537+
self._anchor_cache.add(anchor)
3538+
35213539
self.verbose(f'build anchor ({self.docname}): {anchor}')
35223540
self.body.append(self.start_ac_macro(node, 'anchor'))
35233541
self.body.append(self.build_ac_param(node, '', anchor))
@@ -4118,6 +4136,24 @@ def escape_cdata(self, data):
41184136

41194137
return ConfluenceBaseTranslator.encode(self, data)
41204138

4139+
def _delayed_anchor_inject(self, node):
4140+
"""
4141+
add delayed anchors into a node
4142+
4143+
While it would be nice to add anchors when processing targets
4144+
immediately, Confluence renders anchors in a way where they can result
4145+
in extra newlines. A trick that appears to work is for some anchors,
4146+
we can delay adding them into the body until we build a block (e.g. a
4147+
paragraph) to avoid any extra spacing.
4148+
4149+
Args:
4150+
node: the node to add the anchor into
4151+
"""
4152+
4153+
while self._pending_anchors:
4154+
new_anchor_id = self._pending_anchors.pop()
4155+
self._build_anchor(node, new_anchor_id)
4156+
41214157
# ##########################################################################
41224158
# # #
41234159
# # deprecated helpers #

0 commit comments

Comments
 (0)