123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113openAst.UtilopenStdcompatletrecremove_linksinline=matchinlinewith|Concat(attr,inlines)->Concat(attr,List.mapremove_linksinlines)|Emph(attr,inline)->Emph(attr,remove_linksinline)|Strong(attr,inline)->Emph(attr,remove_linksinline)|Link(_,link)->link.label|Image(attr,link)->Image(attr,{linkwithlabel=remove_linkslink.label})|Hard_break_|Soft_break_|Html_|Code_|Text_->inlineletheaders=letremove_links_f=remove_linksinfun?(remove_links=false)doc->letheaders=ref[]inletrecloopblocks=List.iter(function|Heading(attr,level,inline)->letinline=ifremove_linksthenremove_links_finlineelseinlineinheaders:=(attr,level,inline)::!headers|Blockquote(_,blocks)->loopblocks|List(_,_,_,block_lists)->List.iterloopblock_lists|Paragraph_|Thematic_break_|Html_block_|Definition_list_|Code_block_|Table_->())blocksinloopdoc;List.rev!headers(* Given a list of headers — in the order of the document — go to the
requested subsection. We first seek for the [number]th header at
[level]. *)letrecfind_startheaderslevelnumbersubsections=matchheaderswith|(_,header_level,_)::tlwhenheader_level>level->(* Skip, right [level]-header not yet reached. *)ifnumber=0then(* Assume empty section at [level], do not consume token. *)matchsubsectionswith|[]->headers(* no subsection to find *)|n::subsections->find_startheaders(level+1)nsubsectionselsefind_starttllevelnumbersubsections|(_,header_level,_)::tlwhenheader_level=level->(* At proper [level]. Have we reached the [number] one? *)ifnumber<=1thenmatchsubsectionswith|[]->tl(* no subsection to find *)|n::subsections->find_starttl(level+1)nsubsectionselsefind_starttllevel(number-1)subsections|_->(* Sought [level] has not been found in the current section *)[]letunordered_listitems=List([],Bullet'*',Tight,items)letfind_idattributes=List.find_map(functionk,vwhenString.equal"id"k->Somev|_->None)attributesletlinkattributeslabel=letinline=matchfind_idattributeswith|None->label|Someid->Link([],{label;destination="#"^id;title=None})inParagraph([],inline)letrecmake_toc(headers:('attr*int*'ainline)list)~min_level~max_level=matchheaderswith|_whenmin_level>max_level->([],headers)|[]->([],[])|(_,level,_)::_whenlevel<min_level->([],headers)|(_,level,_)::tlwhenlevel>max_level->make_toctl~min_level~max_level|(attr,level,t)::tlwhenlevel=min_level->letsub_toc,tl=make_toctl~min_level:(min_level+1)~max_levelinlettoc_entry=matchsub_tocwith|[]->[linkattrt]|_->[linkattrt;unordered_listsub_toc]inlettoc,tl=make_toctl~min_level~max_levelin(toc_entry::toc,tl)|_->letsub_toc,tl=make_tocheaders~min_level:(min_level+1)~max_levelinlettoc,tl=make_toctl~min_level~max_levelin([unordered_listsub_toc]::toc,tl)lettoc?(start=[])?(depth=2)doc=ifdepth<1theninvalid_arg"Omd.toc: ~depth must be >= 1";letheaders=headers~remove_links:truedocinletheaders=matchstartwith|[]->headers|number::_whennumber<0->invalid_arg"Omd.toc: level 1 start must be >= 0"|number::subsections->find_startheaders1numbersubsectionsinletlen=List.lengthstartinlettoc,_=make_tocheaders~min_level:(len+1)~max_level:(len+depth)inmatchtocwith[]->[]|_->[unordered_listtoc]