123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569(*
* Copyright (c) 2010 Thomas Gazagnaire <thomas@gazagnaire.org>
* Copyright (c) 2013 Anil Madhavapeddy <anil@recoil.org>
* Copyright (c) 2015 David Sheets <sheets@alum.mit.edu>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*)let(@@)fx=fxlet(|>)xf=fxtypet=Xml.tletvoid_elements=["img";"input";"link";"meta";"br";"hr";"source";"wbr";"param";"embed";"base";"area";"col";"track";"keygen";]typenode=?cls:string->?id:string->?attrs:(string*string)list->t->tlettagname?cls?id?(attrs=[])t=letattrs=matchidwithNone->attrs|Somei->("id",i)::attrsinletattrs=matchclswithNone->attrs|Somec->("class",c)::attrsinXml.tagname~attrstletempty:t=[]letdiv=tag"div"letspan=tag"span"letadd_oattrnameattrattrs=matchattrwithNone->attrs|Somei->(name,i)::attrsletadd_uattrnameattrattrs=matchattrwithNone->attrs|Somei->(name,Uri.to_stringi)::attrsletadd_iattrnameattrattrs=matchattrwithNone->attrs|Somei->(name,string_of_inti)::attrsletadd_battrnameattrattrs=ifattrthen(name,name)::attrselseattrsletinput?cls?id?(attrs=[])?tyv=letattrs=("value",v)::add_oattr"type"tyattrsintag"input"?cls?id~attrsemptyletbr=tag"br"emptylethr=tag"hr"emptyletsource?media?tyuri=letattrs=add_oattr"media"media[]|>add_oattr"type"tyintag"source"empty~attrs:(("src",Uri.to_stringuri)::attrs)letwbr=tag"wbr"emptyletparam~namev=tag"param"empty~attrs:[("name",name);("value",v)]letembed?width?height?ty?(attrs=[])uri=letattrs=attrs|>add_iattr"width"width|>add_iattr"height"height|>add_oattr"type"tyintag"embed"empty~attrs:(("src",Uri.to_stringuri)::attrs)letcol?cls?style?(attrs=[])n=letattrs=("span",string_of_intn)::attrsintag"col"empty?cls~attrs:(add_oattr"style"styleattrs)lettrack?(default=false)?labelkinduri=letattrs=add_battr"default"default[]|>add_oattr"label"labelinletattrs=matchkindwith|`Captions->("kind","captions")::attrs|`Chapters->("kind","chapters")::attrs|`Descriptions->("kind","descriptions")::attrs|`Metadata->("kind","metadata")::attrs|`Subtitleslang->("kind","subtitles")::("srclang",lang)::attrsinletattrs=("src",Uri.to_stringuri)::attrsintag"track"empty~attrsletkeygen?(autofocus=false)?(disabled=false)?form?(challenge=true)?(keytype=`RSA)name=letattrs=add_battr"autofocus"autofocus[]|>add_battr"disabled"disabled|>add_oattr"form"form|>add_battr"challenge"challengeinletattrs=matchkeytypewith|`RSA->("keytype","rsa")::attrs|`DSA->("keytype","dsa")::attrs|`EC->("keytype","ec")::attrsintag"keygen"empty~attrs:(("name",name)::attrs)lethtml=tag"html"letfooter=tag"footer"letheader=tag"header"lethead=tag"head"lettitle=tag"title"letbody=tag"body"letnav=tag"nav"lettr=tag"tr"letth=tag"th"lettd=tag"td"letarticle=tag"article"letsection=tag"section"letaddress=tag"address"letlist=List.concatletsome=functionNone->empty|Somex->xleti=tag"i"letp=tag"p"lettt=tag"tt"letaside=tag"aside"letpre=tag"pre"letmain=tag"main"typecors=[`anonymous|`use_credentials]letstring_of_corsc=matchcwith|`anonymous->"anonymous"|`use_credentials->"use-credentials"letadd_ocrossorigincoattrs=matchcowith|Somes->("crossorigin",string_of_corss)::attrs|None->attrsletlink?cls?id?(attrs=[])?title?media?ty?rel?integrity?crossoriginhref=letattrs=add_oattr"media"mediaattrs|>add_oattr"title"title|>add_oattr"rel"rel|>add_oattr"type"ty|>add_oattr"integrity"integrity|>add_ocrossorigincrossoriginintag"link"empty?cls?id~attrs:(("href",Uri.to_stringhref)::attrs)letbase?cls?id?(attrs=[])?targethref=tag"base"empty?cls?id~attrs:(("href",Uri.to_stringhref)::add_oattr"target"targetattrs)letmeta?cls?id?name?content?charsetattrs=tag"meta"empty?cls?id~attrs:(add_oattr"name"name(add_oattr"content"content(add_oattr"charset"charsetattrs)))letblockquote?cls?id?(attrs=[])?citex=tag"blockquote"?cls?id~attrs:(add_uattr"cite"citeattrs)xletfigure?cls?id?(attrs=[])?figcaptionx=letx=matchfigcaptionwithNone->x|Somei->tag"figcaption"Xml.(i++x)intag"figure"?cls?id~attrsxletem=tag"em"letstrong=tag"strong"lets=tag"s"letcite=tag"cite"letcode=tag"code"letvar=tag"var"letsamp=tag"samp"letkbd=tag"kbd"letsub=tag"sub"letsup=tag"sup"letb=tag"b"letu=tag"u"letmark=tag"mark"letbdi=tag"bdi"letbdo=tag"bdo"letq?cls?id?(attrs=[])?citex=letattrs=matchcitewith|None->attrs|Somei->("cite",Uri.to_stringi)::attrsintag"q"?cls?id~attrsxletdfn?cls?id?(attrs=[])?titlex=tag"dfn"?cls?id~attrs:(add_oattr"title"titleattrs)xletabbr?cls?id?(attrs=[])?titlex=tag"abbr"?cls?id~attrs:(add_oattr"title"titleattrs)xletdata?cls?id?(attrs=[])~valuex=tag"data"?cls?id~attrs:(("value",value)::attrs)xlettime?cls?id?(attrs=[])?datetimex=tag"time"?cls?id~attrs:(add_oattr"datetime"datetimeattrs)xletruby=tag"ruby"letrb=tag"rb"letrt=tag"rt"letrtc=tag"rtc"letrp=tag"rp"letins?cls?id?(attrs=[])?cite?datetimex=tag"ins"?cls?id~attrs:(add_oattr"datetime"datetime(add_uattr"cite"citeattrs))xletdel?cls?id?(attrs=[])?cite?datetimex=tag"del"?cls?id~attrs:(add_oattr"datetime"datetime(add_uattr"cite"citeattrs))xletnil=emptyletconcat=listletli?cls?id?attrsx=tag?cls?id?attrs"li"xletdt?cls?id?attrsx=tag?cls?id?attrs"dt"xletdd?cls?id?attrsx=tag?cls?id?attrs"dd"xletul?(add_li=true)?cls?id?attrs?liclsls=letls=ifadd_lithenList.map(funx->li?cls:liclsx)lselselsintag?cls?id?attrs"ul"(listls)letol?(add_li=false)?cls?id?attrs?liclsls=letls=ifadd_lithenList.map(funx->li?cls:liclsx)lselselsintag?cls?id?attrs"ol"(listls)letdl?(add_dtdd=true)?cls?id?attrs?dtcls?ddclslss=letlss=ifadd_dtddthenList.map(fun(t,d)->list[dt?cls:dtclst;dd?cls:ddclsd])lsselseList.map(fun(t,d)->list[t;d])lssintag?cls?id?attrs"dl"(listlss)leth1=tag"h1"leth2=tag"h2"leth3=tag"h3"leth4=tag"h4"leth5=tag"h5"leth6=tag"h6"letsmall=tag"small"letdoctype="<!DOCTYPE html>"letrecgenerate_signalssignals=function|`Datas->`Datas::signals|`El(tag,children)->(letsignals=`El_starttag::signalsinletsignals=List.fold_leftgenerate_signalssignalschildreninmatchsignalswith|`El_start((_,tag),_)::_whenList.memtagvoid_elements->`El_end::signals|[]|(`Data_|`Dtd_|`El_end)::_->`El_end::signals|`El_start_::_->`El_end::`Data""::signals)letoutput?(nl=false)?(indent=None)?(ns_prefix=fun_->None)destt=letappendtree=letsignals=generate_signals[]treeinletout=Xml.make_output~decl:false~nl~indent~ns_prefixdestinXml.outputout(`DtdNone);List.(iter(Xml.outputout)(revsignals))inList.iterappendtletoutput_doc?(nl=false)?(indent=None)?(ns_prefix=fun_->None)destt=(* This could build an Xmlm.output and use `Dtd to set the DOCTYPE. *)letdoctype=doctype^"\n"in(matchdestwith|`Bufferbuf->Buffer.add_stringbufdoctype|`Channeloc->output_stringocdoctype|`Funf->letlen=String.lengthdoctypeinfori=0tolen-1dof(int_of_chardoctype.[i])done);output~nl~indent~ns_prefixdesttletto_stringt=letbuf=Buffer.create4096inoutput_doc(`Bufferbuf)t;Buffer.contentsbufletof_string?encstr=Xml.of_string~entity:Xhtml.entity?encstrtyperel=[`alternate|`author|`bookmark|`help|`license|`next|`nofollow|`noreferrer|`prefetch|`prev|`search|`tag]letstring_of_rel=function|`alternate->"alternate"|`author->"author"|`bookmark->"bookmark"|`help->"help"|`license->"license"|`next->"next"|`nofollow->"nofollow"|`noreferrer->"noreferrer"|`prefetch->"prefetch"|`prev->"prev"|`search->"search"|`tag->"tag"typetarget=[`blank|`parent|`self|`top|`Frameofstring]letstring_of_target=function|`blank->"_blank"|`parent->"_parent"|`self->"_self"|`top->"_top"|`Framen->nleta?cls?(attrs=[])?hreflang?rel?target?ty?title?hrefhtml=letattrs=add_uattr"href"hrefattrsinletattrs=List.map(fun(n,v)->(("",n),v))attrsinletattrs=matchhreflangwith|Someh->(("","hreflang"),h)::attrs|None->attrsinletattrs=matchrelwith|Somerel->(("","rel"),string_of_relrel)::attrs|None->attrsinletattrs=matchtargetwith|Somet->(("","target"),string_of_targett)::attrs|None->attrsinletattrs=matchtywithSomet->(("","type"),t)::attrs|None->attrsinletattrs=matchtitlewithSomet->(("","title"),t)::attrs|None->attrsinletattrs=matchclswithSomec->(("","class"),c)::attrs|None->attrsin[`El((("","a"),attrs),html)]letimg?alt?width?height?ismap?title?cls?crossorigin?(attrs=[])src=letattrs=List.map(fun(n,v)->(("",n),v))attrsinletattrs=(("","src"),Uri.to_stringsrc)::attrsinletattrs=matchaltwithSomet->(("","alt"),t)::attrs|None->attrsinletattrs=matchwidthwith|Somew->(("","width"),string_of_intw)::attrs|None->attrsinletattrs=matchheightwith|Someh->(("","height"),string_of_inth)::attrs|None->attrsinletattrs=matchtitlewithSomet->(("","title"),t)::attrs|None->attrsinletattrs=matchclswithSomec->(("","class"),c)::attrs|None->attrsinletattrs=matchcrossoriginwith|Somec->(("","crossorigin"),string_of_corsc)::attrs|None->attrsinmatchismapwith|Someu->a~href:u~target:`self[`El((("","img"),(("","ismap"),"")::attrs),[])]|None->[`El((("","img"),attrs),[])]letanchorname=tag"a"~attrs:[("name",name)]emptyletstyle?media?(scoped=false)css=letattrs=add_oattr"media"media[]|>add_battr"scoped"scopedintag"style"(Xml.stringcss)~attrs(* color tweaks for lists *)letinterleaveclassesl=leti=ref0inletn=Array.lengthclassesinletget()=letres=classes.(!imodn)inincri;resinList.map(Xml.tag"div"~attrs:[("class",get())])llethtml_of_strings=Xml.stringsletstring=html_of_stringlethtml_of_inti=Xml.intiletint=html_of_intlethtml_of_floatf=Xml.floatfletfloat=html_of_floattypetable=tarrayarraylethtml_of_table?(headings=false)t=lethd=ifArray.lengtht>0&&headingsthenletl=Array.to_listt.(0)inSome(tr(list@@List.map(funx->thx)l))elseNoneinlettl=ifArray.lengtht>1&&headingsthenList.mapArray.to_list(List.tl(Array.to_listt))elseList.mapArray.to_list(Array.to_listt)inlettl=List.map(funl->tr(list@@List.map(funx->tdx)l))tlinXml.(tag"table"(somehd++listtl))letappend(_to:t)(el:t)=_to@ellet(++)=appendmoduleCreate=structmoduleTags=structtypehtml_list=[`Oloftlist|`Uloftlist]typecolor=Rgbaofchar*char*char*char|Rgbofchar*char*charletcolor_of_string?(fmt=`Hex)s=lets=String.lowercase_asciisinletcoi=char_of_intinletrval=matchfmtwith|`Hex->letfmt'=format_of_string"#%x"inletx=Scanf.sscanfsfmt'(funx->x)inletr,g,b=((xland0xff0000)lsr16,(xland0xff00)lsr8,xland0xff)inRgb(coir,coig,coib)|`Rgb->letfmt'=format_of_string"rgb(%d,%d,%d)"inletr,g,b=Scanf.sscanfsfmt'(funabc->(a,b,c))inRgb(coir,coig,coib)inrvaltypetable_flags=|Headings_fst_col|Headings_fst_row|Sideways|Heading_colorofcolor|Bg_colorofcolortype'atable=[`Trof'atablelist|`Tdof'a*int*int|`Thof'a*int*int]endopenTagstypet=Xml.tletstylesheetcss=Xml.tag"style"~attrs:[("type","text/css")](stringcss)lettheadt=Xml.tag"thead"tlettbodyt=Xml.tag"tbody"tlettable?(flags=[Headings_fst_row])=leth_fst_col=reffalseinleth_fst_row=reffalseinlethdg_c=ref(color_of_string"#eDeDeD")inletbg_c=ref(color_of_string"#fFfFfF")inletside=reffalseinlet()=List.iter(funtag->matchtagwith|Headings_fst_col->h_fst_col:=true|Headings_fst_row->h_fst_row:=true|Heading_colorc->hdg_c:=c|Bg_colorc->bg_c:=c|Sideways->side:=true;())flagsinletaux~rowtbl=letrows=List.maprowtblinletrows=if!sidethenList.mapi(funi_->List.map(funel->List.ntheli)rows)@@List.hdrowselserowsinletcellifyrows=List.map(funr->List.map(funx->tdx)r)rowsinlettr1row=tr(List.flattenrow)inlettrrows=List.concat(List.maptr1rows)inletrows=match(!h_fst_row,!h_fst_col)with|false,false->tbody(tr(cellifyrows))|true,false->lethrow=List.hdrows|>List.map(funx->thx)inletrest=cellify(List.tlrows)inthead(tr1hrow)@tbody(trrest)|false,true->List.map(funr->leth=List.hdrinletrest=List.map(funx->tdx)(List.tlr)inthh::rest)rows|>tr|true,true->lethrow=List.hdrows|>List.map(funx->thx)inletrest=List.tlrows|>List.map(funr->lethcell=List.hdrinletrest=List.flatten@@cellify[List.tlr]inthhcell::rest)inthead(tr1hrow)@tbody(trrest)inXml.tag"table"rowsinauxendletscript?src?ty?charset?integrity?crossoriginbody=letattrs=add_uattr"src"src[]|>add_oattr"type"ty|>add_oattr"charset"charset|>add_oattr"integrity"integrity|>add_ocrossorigincrossoriginintag"script"~attrsbody