11from __future__ import annotations
22
33import re
4+ from bisect import bisect_left
45from dataclasses import dataclass
56from typing import (
67 Any ,
@@ -142,26 +143,32 @@ def parse_link(self, m: Match[str], state: InlineState) -> Optional[int]:
142143 return pos
143144
144145 text = None
146+ text_start = pos
147+ text_end = pos
145148 label , end_pos = parse_link_label (state .src , pos )
146149 if label is None :
147150 if pos <= state .no_close_bracket_before :
148151 state .append_token ({"type" : "text" , "raw" : marker })
149152 return pos
150- text , end_pos = _parse_link_text (state , pos )
151- if text is None :
152- if end_pos > state .no_close_bracket_before :
153- state .no_close_bracket_before = end_pos
153+ close_pos = _find_closing_bracket (state , pos )
154+ if close_pos is None :
155+ if len ( state . src ) > state .no_close_bracket_before :
156+ state .no_close_bracket_before = len ( state . src )
154157 return None
158+ text_start = pos
159+ text_end = close_pos
160+ end_pos = close_pos + 1
155161
156162 assert end_pos is not None
157163
158- if text is None :
164+ if label is not None :
159165 text = label
166+ text_start = pos
167+ text_end = end_pos - 1
160168
161- assert text is not None
162169 body_end_pos = end_pos
163170
164- has_nested_link = not is_image and self . _contains_nested_link ( text , state )
171+ has_nested_link = not is_image and _label_contains_link ( state , text_start , text_end )
165172 if has_nested_link :
166173 return None
167174 if end_pos >= len (state .src ) and label is None :
@@ -179,6 +186,8 @@ def parse_link(self, m: Match[str], state: InlineState) -> Optional[int]:
179186 # standard link [text](<url> "title")
180187 attrs , pos2 = parse_link (state .src , end_pos + 1 )
181188 if pos2 :
189+ if text is None :
190+ text = state .src [text_start :text_end ]
182191 token = self .__parse_link_token (is_image , text , attrs , state )
183192 state .append_token (token )
184193 return pos2
@@ -192,9 +201,13 @@ def parse_link(self, m: Match[str], state: InlineState) -> Optional[int]:
192201 label = label2
193202
194203 if label is None :
195- _mark_no_link_before (state , body_end_pos )
196- return None
197-
204+ ref_links = state .env .get ("ref_links" )
205+ if not ref_links :
206+ _mark_no_link_before (state , body_end_pos )
207+ return None
208+ if text is None :
209+ text = state .src [text_start :text_end ]
210+ label = text
198211 ref_links = state .env .get ("ref_links" )
199212 if not ref_links :
200213 _mark_no_link_before (state , body_end_pos )
@@ -203,6 +216,8 @@ def parse_link(self, m: Match[str], state: InlineState) -> Optional[int]:
203216 key = unikey (label )
204217 env = ref_links .get (key )
205218 if env :
219+ if text is None :
220+ text = state .src [text_start :text_end ]
206221 attrs = {"url" : env ["url" ], "title" : env .get ("title" )}
207222 token = self .__parse_link_token (is_image , text , attrs , state )
208223 token ["ref" ] = key
@@ -212,29 +227,6 @@ def parse_link(self, m: Match[str], state: InlineState) -> Optional[int]:
212227 _mark_no_link_before (state , body_end_pos )
213228 return None
214229
215- def _contains_nested_link (self , text : str , state : InlineState ) -> bool :
216- if "[" not in text :
217- return False
218- if "](" not in text and "][" not in text and not state .env .get ("ref_links" ):
219- return False
220-
221- sc = self .compile_sc (["link" ])
222- nested_state = state .copy ()
223- nested_state .src = text
224- pos = 0
225- while pos < len (text ):
226- m = sc .search (text , pos )
227- if not m :
228- return False
229-
230- marker = m .group (0 )
231- if marker == "[" and (m .start () == 0 or text [m .start () - 1 ] != "!" ):
232- if _is_link_like (text , m .end (), nested_state ):
233- return True
234- pos = m .start () + 1
235-
236- return False
237-
238230 def __parse_link_token (
239231 self ,
240232 is_image : bool ,
@@ -849,56 +841,89 @@ def _merge_text_tokens(tokens: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
849841 return result
850842
851843
852- def _parse_link_text (state : InlineState , pos : int ) -> Tuple [Optional [str ], int ]:
853- close_pos = _find_closing_bracket (state , pos )
854- if close_pos is None :
855- return None , len (state .src )
856- return state .src [pos :close_pos ], close_pos + 1
857-
858-
859844def _mark_no_link_before (state : InlineState , end_pos : int ) -> None :
860845 if end_pos > state .no_link_before :
861846 state .no_link_before = end_pos
862847
863848
864849def _find_closing_bracket (state : InlineState , pos : int ) -> Optional [int ]:
850+ return _get_closing_bracket_map (state ).get (pos )
851+
852+
853+ def _label_contains_link (state : InlineState , start : int , end : int ) -> bool :
854+ if start >= end :
855+ return False
856+
857+ starts , suffix_min_ends = _get_link_range_index (state )
858+ index = bisect_left (starts , start )
859+ return index < len (starts ) and starts [index ] < end and suffix_min_ends [index ] <= end
860+
861+
862+ def _get_link_range_index (state : InlineState ) -> Tuple [List [int ], List [int ]]:
863+ cache = state .link_ranges .get (id (state .src ))
864+ if cache is not None and cache [0 ] is state .src :
865+ return cache [1 ], cache [2 ]
866+
867+ pairs = _get_closing_bracket_map (state )
868+ ranges : List [Tuple [int , int ]] = []
869+ for label_start , close_pos in pairs .items ():
870+ opener = label_start - 1
871+ if opener > 0 and state .src [opener - 1 ] == "!" :
872+ continue
873+ link_end = _find_link_range_end (state .src , label_start , close_pos , state )
874+ if link_end is not None :
875+ ranges .append ((opener , link_end ))
876+
877+ ranges .sort ()
878+ starts = [start for start , _end in ranges ]
879+ suffix_min_ends = [0 ] * len (ranges )
880+ min_end = len (state .src ) + 1
881+ for index in range (len (ranges ) - 1 , - 1 , - 1 ):
882+ end = ranges [index ][1 ]
883+ if end < min_end :
884+ min_end = end
885+ suffix_min_ends [index ] = min_end
886+
887+ state .link_ranges [id (state .src )] = (state .src , starts , suffix_min_ends )
888+ return starts , suffix_min_ends
889+
890+
891+ def _get_closing_bracket_map (state : InlineState ) -> Dict [int , int ]:
865892 cache = state .link_brackets .get (id (state .src ))
866893 if cache is not None and cache [0 ] is state .src :
867- return cache [1 ]. get ( pos )
894+ return cache [1 ]
868895
869896 pairs = _build_closing_bracket_map (state .src )
870897 state .link_brackets [id (state .src )] = (state .src , pairs )
871- return pairs .get (pos )
872-
898+ return pairs
873899
874- def _is_link_like (src : str , pos : int , state : InlineState ) -> bool :
875- label , end_pos = parse_link_label (src , pos )
876- if label is None :
877- label , end_pos = _parse_link_text (state , pos )
878- if label is None :
879- return False
880900
881- assert label is not None
882- assert end_pos is not None
901+ def _find_link_range_end (src : str , label_start : int , close_pos : int , state : InlineState ) -> Optional [int ]:
902+ end_pos = close_pos + 1
903+ if end_pos < len (src ):
904+ marker = src [end_pos ]
905+ if marker == "(" :
906+ _attrs , new_pos = parse_link (src , end_pos + 1 )
907+ return new_pos
883908
884- if end_pos >= len ( src ) :
885- ref_links = state . env . get ( "ref_links" )
886- return bool ( ref_links and unikey ( label ) in ref_links )
887-
888- marker = src [ end_pos ]
889- if marker == "(" :
890- _attrs , new_pos = parse_link ( src , end_pos + 1 )
891- return bool ( new_pos )
909+ if marker == "[" :
910+ label , new_pos = parse_link_label ( src , end_pos + 1 )
911+ if not new_pos :
912+ return None
913+ if label :
914+ ref_label = label
915+ else :
916+ ref_label = src [ label_start : close_pos ]
892917
893- if marker == "[" :
894- label2 , new_pos = parse_link_label (src , end_pos + 1 )
895- if not new_pos :
896- return False
897- if label2 :
898- label = label2
918+ ref_links = state .env .get ("ref_links" )
919+ if ref_links and unikey (ref_label ) in ref_links :
920+ return new_pos
921+ return None
899922
900923 ref_links = state .env .get ("ref_links" )
901- return bool (ref_links and unikey (label ) in ref_links )
924+ if ref_links and unikey (src [label_start :close_pos ]) in ref_links :
925+ return end_pos
926+ return None
902927
903928
904929def _build_closing_bracket_map (src : str ) -> Dict [int , int ]:
0 commit comments