123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348openAst.Impltypeelement_type=|Inline|Block|Tabletypet=|Elementofelement_type*string*attributes*toption|Textofstring|Rawofstring|Null|Concatoft*tleteltetypenameattrschilds=Element(etype,name,attrs,childs)lettexts=Textsletraws=Rawsletconcatt1t2=match(t1,t2)withNull,t|t,Null->t|_->Concat(t1,t2)letconcat_mapfl=List.fold_left(funaccux->concataccu(fx))Nulllletconcat_map2fl1l2=List.fold_left2(funaccuxy->concataccu(fxy))Nulll1l2(* only convert when "necessary" *)lethtmlentitiess=letb=Buffer.create(String.lengths)inletrecloopi=ifi>=String.lengthsthenBuffer.contentsbelsebeginbeginmatchs.[i]with|'"'->Buffer.add_stringb"""|'&'->Buffer.add_stringb"&"|'<'->Buffer.add_stringb"<"|'>'->Buffer.add_stringb">"|c->Buffer.add_charbcend;loop(succi)endinloop0letadd_attrs_to_bufferbufattrs=letf(k,v)=Printf.bprintfbuf" %s=\"%s\""k(htmlentitiesv)inList.iterfattrsletrecadd_to_bufferbuf=function|Element(eltype,name,attrs,None)->Printf.bprintfbuf"<%s%a />"nameadd_attrs_to_bufferattrs;ifeltype=BlockthenBuffer.add_charbuf'\n'|Element(eltype,name,attrs,Somec)->Printf.bprintfbuf"<%s%a>%s%a</%s>%s"nameadd_attrs_to_bufferattrs(matcheltypewithTable->"\n"|_->"")add_to_buffercname(matcheltypewithTable|Block->"\n"|_->"")|Texts->Buffer.add_stringbuf(htmlentitiess)|Raws->Buffer.add_stringbufs|Null->()|Concat(t1,t2)->add_to_bufferbuft1;add_to_bufferbuft2letescape_uris=letb=Buffer.create(String.lengths)inString.iter(function|('!'|'*'|'\''|'('|')'|';'|':'|'@'|'='|'+'|'$'|','|'/'|'?'|'%'|'#'|'A'..'Z'|'a'..'z'|'0'..'9'|'-'|'_'|'.'|'~'|'&')asc->Buffer.add_charbc|_asc->Printf.bprintfb"%%%2X"(Char.codec))s;Buffer.contentsblettrim_start_whileps=letstart=reftrueinletb=Buffer.create(String.lengths)inUutf.String.fold_utf_8(fun()_->function|`Malformed_->Buffer.add_stringbs|`Ucharuwhenpu&&!start->()|`Ucharuwhen!start->start:=false;Uutf.Buffer.add_utf_8bu|`Ucharu->Uutf.Buffer.add_utf_8bu)()s;Buffer.contentsbletunderscore=Uchar.of_char'_'lethyphen=Uchar.of_char'-'letperiod=Uchar.of_char'.'letis_white_space=Uucp.White.is_white_spaceletis_alphabetic=Uucp.Alpha.is_alphabeticletis_hex_digit=Uucp.Num.is_hex_digitmoduleIdentifiers:sigtypetvalempty:tvaltouch:string->t->int*t(** Bump the frequency count for the given string.
It returns the previous count (before bumping) *)end=structmoduleSMap=Map.Make(String)typet=intSMap.tletempty=SMap.emptyletcountst=matchSMap.find_optstwithNone->0|Somex->xletincrst=SMap.adds(countst+1)tlettouchst=letcount=countstin(count,incrst)end(* Based on pandoc algorithm to derive id's.
See: https://pandoc.org/MANUAL.html#extension-auto_identifiers *)letslugifys=lets=trim_start_while(func->not(is_alphabeticc))sinletlength=String.lengthsinletb=Buffer.createlengthinletlast_is_ws=reffalseinletadd_to_bufferu=if!last_is_ws=truethenbeginUutf.Buffer.add_utf_8b(Uchar.of_char'-');last_is_ws:=falseend;Uutf.Buffer.add_utf_8buinletfold()_=function|`Malformed_->add_to_bufferUutf.u_rep|`Ucharuwhenis_white_spaceu&¬!last_is_ws->last_is_ws:=true|`Ucharuwhenis_white_spaceu&&!last_is_ws->()|`Ucharu->(ifis_alphabeticu||is_hex_digituthenmatchUucp.Case.Map.to_loweruwith|`Self->add_to_bufferu|`Ucharsus->List.iteradd_to_bufferus);ifu=underscore||u=hyphen||u=periodthenadd_to_bufferuinUutf.String.fold_utf_8fold()s;Buffer.contentsbletto_plain_textt=letbuf=Buffer.create1024inletrecgo:_inline->unit=function|Concat(_,l)->List.itergol|Text(_,t)|Code(_,t)->Buffer.add_stringbuft|Emph(_,i)|Strong(_,i)|Link(_,{label=i;_})|Image(_,{label=i;_})->goi|Hard_break_|Soft_break_->Buffer.add_charbuf' '|Html_->()ingot;Buffer.contentsbufletnl=Raw"\n"letrecurllabeldestinationtitleattrs=letattrs=matchtitlewithNone->attrs|Sometitle->("title",title)::attrsinletattrs=("href",escape_uridestination)::attrsineltInline"a"attrs(Some(inlinelabel))andimglabeldestinationtitleattrs=letattrs=matchtitlewithNone->attrs|Sometitle->("title",title)::attrsinletattrs=("src",escape_uridestination)::("alt",to_plain_textlabel)::attrsineltInline"img"attrsNoneandinline=function|Ast.Impl.Concat(_,l)->concat_mapinlinel|Text(_,t)->textt|Emph(attr,il)->eltInline"em"attr(Some(inlineil))|Strong(attr,il)->eltInline"strong"attr(Some(inlineil))|Code(attr,s)->eltInline"code"attr(Some(texts))|Hard_breakattr->concat(eltInline"br"attrNone)nl|Soft_break_->nl|Html(_,body)->rawbody|Link(attr,{label;destination;title})->urllabeldestinationtitleattr|Image(attr,{label;destination;title})->imglabeldestinationtitleattrletalignment_attributes=function|Default->[]|Left->[("align","left")]|Right->[("align","right")]|Centre->[("align","center")]lettable_headerheaders=eltTable"thead"[](Some(eltTable"tr"[](Some(concat_map(fun(header,alignment)->letattrs=alignment_attributesalignmentineltBlock"th"attrs(Some(inlineheader)))headers))))lettable_bodyheadersrows=eltTable"tbody"[](Some(concat_map(funrow->eltTable"tr"[](Some(concat_map2(fun(_,alignment)cell->letattrs=alignment_attributesalignmentineltBlock"td"attrs(Some(inlinecell)))headersrow)))rows))letrecblock~auto_identifiers=function|Blockquote(attr,q)->eltBlock"blockquote"attr(Some(concatnl(concat_map(block~auto_identifiers)q)))|Paragraph(attr,md)->eltBlock"p"attr(Some(inlinemd))|List(attr,ty,sp,bl)->letname=matchtywithOrdered_->"ol"|Bullet_->"ul"inletattr=matchtywith|Ordered(n,_)whenn<>1->("start",string_of_intn)::attr|_->attrinletlit=letblock't=match(t,sp)with|Paragraph(_,t),Tight->concat(inlinet)nl|_->block~auto_identifierstinletnl=ifsp=TightthenNullelsenlineltBlock"li"[](Some(concatnl(concat_mapblock't)))ineltBlocknameattr(Some(concatnl(concat_maplibl)))|Code_block(attr,label,code)->letcode_attr=ifString.trimlabel=""then[]else[("class","language-"^label)]inletc=textcodeineltBlock"pre"attr(Some(eltInline"code"code_attr(Somec)))|Thematic_breakattr->eltBlock"hr"attrNone|Html_block(_,body)->rawbody|Heading(attr,level,text)->letname=matchlevelwith|1->"h1"|2->"h2"|3->"h3"|4->"h4"|5->"h5"|6->"h6"|_->"p"ineltBlocknameattr(Some(inlinetext))|Definition_list(attr,l)->letf{term;defs}=concat(eltBlock"dt"[](Some(inlineterm)))(concat_map(funs->eltBlock"dd"[](Some(inlines)))defs)ineltBlock"dl"attr(Some(concat_mapfl))|Table(attr,headers,[])->eltTable"table"attr(Some(table_headerheaders))|Table(attr,headers,rows)->eltTable"table"attr(Some(concat(table_headerheaders)(table_bodyheadersrows)))letof_doc?(auto_identifiers=true)doc=letidentifiers=Identifiers.emptyinletfidentifiers=function|Heading(attr,level,text)->letattr,identifiers=if(notauto_identifiers)||List.mem_assoc"id"attrthen(attr,identifiers)elseletid=slugify(to_plain_texttext)in(* Default identifier if empty. It matches what pandoc does. *)letid=ifid=""then"section"elseidinletcount,identifiers=Identifiers.touchididentifiersinletid=ifcount=0thenidelsePrintf.sprintf"%s-%i"idcountin(("id",id)::attr,identifiers)in(Heading(attr,level,text),identifiers)|_asc->(c,identifiers)inlethtml,_=List.fold_left(fun(accu,ids)x->letx',ids=fidsxinletel=concataccu(block~auto_identifiersx')in(el,ids))(Null,identifiers)docinhtmlletto_stringt=letbuf=Buffer.create1024inadd_to_bufferbuft;Buffer.contentsbuf