@@ -616,7 +616,9 @@ class NotebookParser(rst.Parser):
616616 def get_transforms (self ):
617617 """List of transforms for documents parsed by this parser."""
618618 return rst .Parser .get_transforms (self ) + [
619- ProcessLocalLinks , ReplaceAlertDivs ]
619+ CreateNotebookSectionAnchors ,
620+ ReplaceAlertDivs ,
621+ ]
620622
621623 def parse (self , inputstring , document ):
622624 """Parse *inputstring*, write results to *document*.
@@ -1005,12 +1007,12 @@ class ProcessLocalLinks(docutils.transforms.Transform):
10051007 """Process links to local files.
10061008
10071009 Marks local files to be copied to the HTML output directory and
1008- turns links to local notebooks into ``:doc:``/``:ref:`` links.
1010+ turns links to source files into ``:doc:``/``:ref:`` links.
10091011
10101012 Links to subsections are possible with ``...#Subsection-Title``.
10111013 These links use the labels created by CreateSectionLabels.
10121014
1013- Links to subsections use ``:ref:``, links to whole notebooks use
1015+ Links to subsections use ``:ref:``, links to whole source files use
10141016 ``:doc:``. Latter can be useful if you have an ``index.rst`` but
10151017 also want a distinct ``index.ipynb`` for use with Jupyter.
10161018 In this case you can use such a link in a notebook::
@@ -1023,24 +1025,37 @@ class ProcessLocalLinks(docutils.transforms.Transform):
10231025
10241026 """
10251027
1026- default_priority = 400 # Should probably be adjusted?
1028+ default_priority = 500 # After AnonymousHyperlinks (440)
10271029
10281030 _subsection_re = re .compile (r'^([^#]+)((\.[^#]+)#.+)$' )
10291031
10301032 def apply (self ):
10311033 env = self .document .settings .env
10321034 for node in self .document .traverse (docutils .nodes .reference ):
1033- uri = node .get ('refuri' , '' )
1034- if not uri :
1035- continue # No URI (e.g. named reference)
1036- elif '://' in uri :
1035+ # NB: Anonymous hyperlinks must be already resolved at this point!
1036+ refuri = node .get ('refuri' )
1037+ if not refuri :
1038+ refname = node .get ('refname' )
1039+ if refname :
1040+ refid = self .document .nameids .get (refname )
1041+ else :
1042+ # NB: This can happen for anonymous hyperlinks
1043+ refid = node .get ('refid' )
1044+ target = self .document .ids .get (refid )
1045+ if not target :
1046+ continue # No corresponding target, Sphinx may warn later
1047+ refuri = target .get ('refuri' )
1048+ if not refuri :
1049+ continue # Target doesn't have URI
1050+
1051+ if '://' in refuri :
10371052 continue # Not a local link
1038- elif uri .startswith ('#' ) or uri .startswith ('mailto:' ):
1053+ elif refuri .startswith ('#' ) or refuri .startswith ('mailto:' ):
10391054 continue # Nothing to be done
10401055
10411056 # NB: We look for "fragment identifier" before unquoting
1042- fragment = self ._subsection_re .match (uri )
1043- uri = unquote (uri )
1057+ fragment = self ._subsection_re .match (refuri )
1058+ refuri = unquote (refuri )
10441059 for suffix in env .config .source_suffix :
10451060 if fragment :
10461061 if fragment .group (3 ).lower () == suffix .lower ():
@@ -1051,17 +1066,17 @@ def apply(self):
10511066 refdomain = 'std'
10521067 break
10531068 else :
1054- if uri .lower ().endswith (suffix .lower ()):
1055- target = uri [:- len (suffix )]
1069+ if refuri .lower ().endswith (suffix .lower ()):
1070+ target = refuri [:- len (suffix )]
10561071 target_ext = ''
10571072 reftype = 'doc'
10581073 refdomain = None
10591074 break
10601075 else :
10611076 if fragment :
1062- uri = unquote (fragment .group (1 )) + fragment .group (3 )
1077+ refuri = unquote (fragment .group (1 )) + fragment .group (3 )
10631078 file = os .path .normpath (
1064- os .path .join (os .path .dirname (env .docname ), uri ))
1079+ os .path .join (os .path .dirname (env .docname ), refuri ))
10651080 if not os .path .isfile (os .path .join (env .srcdir , file )):
10661081 env .app .warn ('file not found: {!r}' .format (file ),
10671082 env .doc2path (env .docname ))
@@ -1089,15 +1104,28 @@ def apply(self):
10891104 node .replace_self (xref )
10901105
10911106
1092- class CreateSectionLabels (docutils .transforms .Transform ):
1093- """Make labels for each document parsed by Sphinx, each section thereof,
1094- and each Sphinx domain object.
1095-
1096- These labels are referenced in ProcessLocalLinks.
1107+ class CreateNotebookSectionAnchors (docutils .transforms .Transform ):
1108+ """Create section anchors for Jupyter notebooks.
10971109
10981110 Note: Sphinx lower-cases the HTML section IDs, Jupyter doesn't.
1099- This transform creates labels in the Jupyter style for Jupyter
1100- notebooks, but keeps the Sphinx style for all other source files.
1111+ This transform creates anchors in the Jupyter style.
1112+
1113+ """
1114+
1115+ default_priority = 200 # Before CreateSectionLabels (250)
1116+
1117+ def apply (self ):
1118+ for section in self .document .traverse (docutils .nodes .section ):
1119+ title = section .children [0 ].astext ()
1120+ link_id = title .replace (' ' , '-' )
1121+ section ['ids' ] = [link_id ]
1122+
1123+
1124+ class CreateSectionLabels (docutils .transforms .Transform ):
1125+ """Make labels for each document and each section thereof.
1126+
1127+ These labels are referenced in ProcessLocalLinks but can also be
1128+ used manually with ``:ref:``.
11011129
11021130 """
11031131
@@ -1111,11 +1139,7 @@ def apply(self):
11111139 assert section .children
11121140 assert isinstance (section .children [0 ], docutils .nodes .title )
11131141 title = section .children [0 ].astext ()
1114- if file_ext .lower () == '.ipynb' :
1115- link_id = title .replace (' ' , '-' )
1116- section ['ids' ] = [link_id ]
1117- else :
1118- link_id = section ['ids' ][0 ]
1142+ link_id = section ['ids' ][0 ]
11191143 label = '/' + env .docname + file_ext + '#' + link_id
11201144 label = label .lower ()
11211145 env .domaindata ['std' ]['labels' ][label ] = (
@@ -1132,7 +1156,15 @@ def apply(self):
11321156 env .docname , '' )
11331157 i_still_have_to_create_the_document_label = False
11341158
1135- # Create labels for domain-specific object signatures
1159+
1160+ class CreateDomainObjectLabels (docutils .transforms .Transform ):
1161+ """Create labels for domain-specific object signatures."""
1162+
1163+ default_priority = 250 # About the same as CreateSectionLabels
1164+
1165+ def apply (self ):
1166+ env = self .document .settings .env
1167+ file_ext = os .path .splitext (env .doc2path (env .docname ))[1 ]
11361168 for sig in self .document .traverse (sphinx .addnodes .desc_signature ):
11371169 try :
11381170 title = sig ['ids' ][0 ]
@@ -1420,6 +1452,8 @@ def setup(app):
14201452 app .connect ('html-collect-pages' , html_collect_pages )
14211453 app .connect ('env-purge-doc' , env_purge_doc )
14221454 app .add_transform (CreateSectionLabels )
1455+ app .add_transform (CreateDomainObjectLabels )
1456+ app .add_transform (ProcessLocalLinks )
14231457
14241458 # Make docutils' "code" directive (generated by markdown2rst/pandoc)
14251459 # behave like Sphinx's "code-block",
0 commit comments