{-# LANGUAGE OverloadedStrings, CPP #-}{-# OPTIONS_GHC -fno-warn-deprecations #-}{-
Copyright (C) 2006-2010 John MacFarlane <jgm@berkeley.edu>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-}{- |
Module : Text.Pandoc.Writers.HTML
Copyright : Copyright (C) 2006-2010 John MacFarlane
License : GNU GPL, version 2 or above
Maintainer : John MacFarlane <jgm@berkeley.edu>
Stability : alpha
Portability : portable
Conversion of 'Pandoc' documents to HTML.
-}moduleText.Pandoc.Writers.HTML(writeHtml,writeHtmlString)whereimportText.Pandoc.DefinitionimportText.Pandoc.SharedimportText.Pandoc.Writers.SharedimportText.Pandoc.OptionsimportText.Pandoc.TemplatesimportText.Pandoc.Readers.TeXMathimportText.Pandoc.SlidesimportText.Pandoc.Highlighting(highlight,styleToCss,formatHtmlInline,formatHtmlBlock)importText.Pandoc.XML(fromEntities,escapeStringForXML)importNetwork.HTTP(urlEncode)importNumeric(showHex)importData.Char(ord,toLower)importData.List(isPrefixOf,intersperse)importData.String(fromString)importData.Maybe(catMaybes,fromMaybe)importControl.Monad.StateimportText.Blaze.Htmlhiding(contents)importText.Blaze.Internal(preEscapedString)#if MIN_VERSION_blaze_html(0,5,1)importqualifiedText.Blaze.XHtml5asH5#elseimportqualifiedText.Blaze.Html5asH5#endifimportqualifiedText.Blaze.XHtml1.TransitionalasHimportqualifiedText.Blaze.XHtml1.Transitional.AttributesasAimportText.Blaze.Renderer.String(renderHtml)importText.TeXMathimportText.XML.Light.OutputimportSystem.FilePath(takeExtension)importData.MonoidimportData.Aeson(Value)dataWriterState=WriterState{stNotes::[Html]-- ^ List of notes,stMath::Bool-- ^ Math is used in document,stQuotes::Bool-- ^ <q> tag is used,stHighlighting::Bool-- ^ Syntax highlighting is used,stSecNum::[Int]-- ^ Number of current section}defaultWriterState::WriterStatedefaultWriterState=WriterState{stNotes=[],stMath=False,stQuotes=False,stHighlighting=False,stSecNum=[]}-- Helpers to render HTML with the appropriate function.strToHtml::String->HtmlstrToHtml('\'':xs)=preEscapedString"\'"`mappend`strToHtmlxsstrToHtmlxs@(_:_)=casebreak(=='\'')xsof(_,[])->toHtmlxs(ys,zs)->toHtmlys`mappend`strToHtmlzsstrToHtml[]=""-- | Hard linebreak.nl::WriterOptions->Htmlnlopts=ifwriterWrapTextoptsthenpreEscapedString"\n"elsemempty-- | Convert Pandoc document to Html string.writeHtmlString::WriterOptions->Pandoc->StringwriteHtmlStringoptsd=let(body,context)=evalState(pandocToHtmloptsd)defaultWriterStateinifwriterStandaloneoptstheninTemplateoptscontextbodyelserenderHtmlbody-- | Convert Pandoc document to Html structure.writeHtml::WriterOptions->Pandoc->HtmlwriteHtmloptsd=let(body,context)=evalState(pandocToHtmloptsd)defaultWriterStateinifwriterStandaloneoptstheninTemplateoptscontextbodyelsebody-- result is (title, authors, date, toc, body, new variables)pandocToHtml::WriterOptions->Pandoc->StateWriterState(Html,Value)pandocToHtmlopts(Pandocmetablocks)=dometadata<-metaToJSONopts(fmaprenderHtml.blockListToHtmlopts)(fmaprenderHtml.inlineListToHtmlopts)metaletstringifyHTML=escapeStringForXML.stringifyletauthsMeta=mapstringifyHTML$docAuthorsmetaletdateMeta=stringifyHTML$docDatemetaletslideLevel=fromMaybe(getSlideLevelblocks)$writerSlideLeveloptsletsects=hierarchicalize$ifwriterSlideVariantopts==NoSlidesthenblockselseprepSlidesslideLevelblockstoc<-ifwriterTableOfContentsoptsthentableOfContentsoptssectselsereturnNothingblocks'<-liftM(mconcat.intersperse(nlopts))$mapM(elementToHtmlslideLevelopts)sectsst<-getletnotes=reverse(stNotesst)letthebody=blocks'>>footnoteSectionoptsnotesletmath=ifstMathstthencasewriterHTMLMathMethodoptsofLaTeXMathML(Justurl)->H.script!A.src(toValueurl)!A.type_"text/javascript"$memptyMathML(Justurl)->H.script!A.src(toValueurl)!A.type_"text/javascript"$memptyMathJaxurl->H.script!A.src(toValueurl)!A.type_"text/javascript"$casewriterSlideVariantoptsofSlideousSlides->preEscapedString"MathJax.Hub.Queue([\"Typeset\",MathJax.Hub]);"_->memptyJsMath(Justurl)->H.script!A.src(toValueurl)!A.type_"text/javascript"$mempty_->caselookup"mathml-script"(writerVariablesopts)ofJusts|not(writerHtml5opts)->H.script!A.type_"text/javascript"$preEscapedString("/*<![CDATA[*/\n"++s++"/*]]>*/\n")|otherwise->memptyNothing->memptyelsememptyletcontext=(ifstHighlightingstthendefField"highlighting-css"(styleToCss$writerHighlightStyleopts)elseid)$(ifstMathstthendefField"math"(renderHtmlmath)elseid)$defField"quotes"(stQuotesst)$maybeid(defField"toc".renderHtml)toc$defField"author-meta"authsMeta$maybeid(defField"date-meta")(normalizeDatedateMeta)$defField"pagetitle"(stringifyHTML$docTitlemeta)$defField"idprefix"(writerIdentifierPrefixopts)$-- these should maybe be set in pandoc.hsdefField"slidy-url"("http://www.w3.org/Talks/Tools/Slidy2"::String)$defField"slideous-url"("slideous"::String)$defField"revealjs-url"("reveal.js"::String)$defField"s5-url"("s5/default"::String)$defField"html5"(writerHtml5opts)$metadatareturn(thebody,context)inTemplate::TemplateTargeta=>WriterOptions->Value->Html->ainTemplateoptscontextbody=renderTemplate'(writerTemplateopts)$defField"body"(renderHtmlbody)context-- | Like Text.XHtml's identifier, but adds the writerIdentifierPrefixprefixedId::WriterOptions->String->AttributeprefixedIdoptss=casesof""->mempty_->A.id$toValue$writerIdentifierPrefixopts++stoList::(Html->Html)->WriterOptions->([Html]->Html)toListlistopoptsitems=doif(writerIncrementalopts)thenif(writerSlideVariantopts/=RevealJsSlides)then(listop$mconcatitems)!A.class_"incremental"elselistop$mconcat$map(!A.class_"fragment")itemselselistop$mconcatitemsunordList::WriterOptions->[Html]->HtmlunordListopts=toListH.ulopts.toListItemsoptsordList::WriterOptions->[Html]->HtmlordListopts=toListH.olopts.toListItemsoptsdefList::WriterOptions->[Html]->HtmldefListoptsitems=toListH.dlopts(items++[nlopts])-- | Construct table of contents from list of elements.tableOfContents::WriterOptions->[Element]->StateWriterState(MaybeHtml)tableOfContents_[]=returnNothingtableOfContentsoptssects=doletopts'=opts{writerIgnoreNotes=True}contents<-mapM(elementToListItemopts')sectslettocList=catMaybescontentsreturn$ifnulltocListthenNothingelseJust$unordListoptstocList-- | Convert section number to stringshowSecNum::[Int]->StringshowSecNum=concat.intersperse".".mapshow-- | Converts an Element to a list item for a table of contents,-- retrieving the appropriate identifier from state.elementToListItem::WriterOptions->Element->StateWriterState(MaybeHtml)elementToListItemopts(Seclevnum(id',classes,_)headerTextsubsecs)|lev<=writerTOCDepthopts=doletnum'=zipWith(+)num(writerNumberOffsetopts++repeat0)letsectnum=ifwriterNumberSectionsopts&&not(nullnum)&&"unnumbered"`notElem`classesthen(H.span!A.class_"toc-section-number"$toHtml$showSecNumnum')>>preEscapedString" "elsememptytxt<-liftM(sectnum>>)$inlineListToHtmloptsheaderTextsubHeads<-mapM(elementToListItemopts)subsecs>>=return.catMaybesletsubList=ifnullsubHeadsthenmemptyelseunordListoptssubHeads-- in reveal.js, we need #/apples, not #apples:letrevealSlash=['/'|writerSlideVariantopts==RevealJsSlides]return$Just$ifnullid'then(H.a$toHtmltxt)>>subListelse(H.a!A.href(toValue$"#"++revealSlash++writerIdentifierPrefixopts++id')$toHtmltxt)>>subListelementToListItem__=returnNothing-- | Convert an Element to Html.elementToHtml::Int->WriterOptions->Element->StateWriterStateHtmlelementToHtml_slideLevelopts(Blkblock)=blockToHtmloptsblockelementToHtmlslideLevelopts(Seclevelnum(id',classes,keyvals)title'elements)=doletslide=writerSlideVariantopts/=NoSlides&&level<=slideLevelletnum'=zipWith(+)num(writerNumberOffsetopts++repeat0)modify$\st->st{stSecNum=num'}-- update section number-- always use level 1 for slide titlesletlevel'=ifslidethen1elselevellettitleSlide=slide&&level<slideLevelheader'<-iftitle'==[Str"\0"]-- marker for hrulethenreturnmemptyelseblockToHtmlopts(Headerlevel'(id',classes,keyvals)title')letisSec(Sec_____)=TrueisSec(Blk_)=FalseletisPause(Blkx)=x==Para[Str".",Space,Str".",Space,Str"."]isPause_=FalseletfragmentClass=casewriterSlideVariantoptsofRevealJsSlides->"fragment"_->"incremental"letinDivxs=Blk(RawBlock(Format"html")("<div class=\""++fragmentClass++"\">")):(xs++[Blk(RawBlock(Format"html")"</div>")])innerContents<-mapM(elementToHtmlslideLevelopts)$iftitleSlide-- title slides have no content of their ownthenfilterisSecelementselseifslidethencasesplitByisPauseelementsof[]->[](x:xs)->x++concatMapinDivxselseelementsletinNlx=mconcat$nlopts:intersperse(nlopts)x++[nlopts]letclasses'=["titleslide"|titleSlide]++["slide"|slide]++["section"|(slide||writerSectionDivsopts)&&not(writerHtml5opts)]++["level"++showlevel|slide||writerSectionDivsopts]++classesletsecttag=ifwriterHtml5optsthenH5.sectionelseH.divletattr=(id',classes',keyvals)return$iftitleSlidethen(ifwriterSlideVariantopts==RevealJsSlidesthenH5.sectionelseid)$mconcat$(addAttrsoptsattr$secttag$header'):innerContentselseifwriterSectionDivsopts||slidethenaddAttrsoptsattr$secttag$inNl$header':innerContentselsemconcat$intersperse(nlopts)$addAttrsoptsattrheader':innerContents-- | Convert list of Note blocks to a footnote <div>.-- Assumes notes are sorted.footnoteSection::WriterOptions->[Html]->HtmlfootnoteSectionoptsnotes=ifnullnotesthenmemptyelsenlopts>>(container$nlopts>>hrtag>>nlopts>>H.ol(mconcatnotes>>nlopts)>>nlopts)wherecontainerx=ifwriterHtml5optsthenH5.section!A.class_"footnotes"$xelseifwriterSlideVariantopts/=NoSlidesthenH.div!A.class_"footnotes slide"$xelseH.div!A.class_"footnotes"$xhrtag=ifwriterHtml5optsthenH5.hrelseH.hr-- | Parse a mailto link; return Just (name, domain) or Nothing.parseMailto::String->Maybe(String,String)parseMailtos=docasebreak(==':')sof(xs,':':addr)|maptoLowerxs=="mailto"->dolet(name',rest)=span(/='@')addrletdomain=drop1restreturn(name',domain)_->fail"not a mailto: URL"-- | Obfuscate a "mailto:" link.obfuscateLink::WriterOptions->String->String->HtmlobfuscateLinkoptstxts|writerEmailObfuscationopts==NoObfuscation=H.a!A.href(toValues)$toHtmltxtobfuscateLinkoptstxts=letmeth=writerEmailObfuscationoptss'=maptoLower(take7s)++drop7sincaseparseMailtos'of(Just(name',domain))->letdomain'=substitute"."" dot "domainat'=obfuscateChar'@'(linkText,altText)=iftxt==drop7s'-- autolinkthen("e",name'++" at "++domain')else("'"++txt++"'",txt++" ("++name'++" at "++domain'++")")incasemethofReferenceObfuscation->-- need to use preEscapedString or &'s are escaped to &amp; in URLpreEscapedString$"<a href=\""++(obfuscateStrings')++"\">"++(obfuscateStringtxt)++"</a>"JavascriptObfuscation->(H.script!A.type_"text/javascript"$preEscapedString("\n<!--\nh='"++obfuscateStringdomain++"';a='"++at'++"';n='"++obfuscateStringname'++"';e=n+a+h;\n"++"document.write('<a h'+'ref'+'=\"ma'+'ilto'+':'+e+'\">'+"++linkText++"+'<\\/'+'a'+'>');\n// -->\n"))>>H.noscript(preEscapedString$obfuscateStringaltText)_->error$"Unknown obfuscation method: "++showmeth_->H.a!A.href(toValues)$toHtmltxt-- malformed email-- | Obfuscate character as entity.obfuscateChar::Char->StringobfuscateCharchar=letnum=ordcharnumstr=ifevennumthenshownumelse"x"++showHexnum""in"&#"++numstr++";"-- | Obfuscate string using entities.obfuscateString::String->StringobfuscateString=concatMapobfuscateChar.fromEntitiesaddAttrs::WriterOptions->Attr->Html->HtmladdAttrsoptsattrh=foldl(!)h(attrsToHtmloptsattr)attrsToHtml::WriterOptions->Attr->[Attribute]attrsToHtmlopts(id',classes',keyvals)=[prefixedIdoptsid'|not(nullid')]++[A.class_(toValue$unwordsclasses')|not(nullclasses')]++map(\(x,y)->customAttribute(fromStringx)(toValuey))keyvalsimageExts::[String]imageExts=["art","bmp","cdr","cdt","cpt","cr2","crw","djvu","erf","gif","ico","ief","jng","jpg","jpeg","nef","orf","pat","pbm","pcx","pgm","png","pnm","ppm","psd","ras","rgb","svg","tiff","wbmp","xbm","xpm","xwd"]treatAsImage::FilePath->BooltreatAsImagefp=letext=maptoLower$drop1$takeExtensionfpinnullext||ext`elem`imageExts-- | Convert Pandoc block element to HTML.blockToHtml::WriterOptions->Block->StateWriterStateHtmlblockToHtml_Null=returnmemptyblockToHtmlopts(Plainlst)=inlineListToHtmloptslst-- title beginning with fig: indicates that the image is a figureblockToHtmlopts(Para[Imagetxt(s,'f':'i':'g':':':tit)])=doimg<-inlineToHtmlopts(Imagetxt(s,tit))lettocapt=ifwriterHtml5optsthenH5.figcaptionelseH.p!A.class_"caption"capt<-ifnulltxtthenreturnmemptyelsetocapt`fmap`inlineListToHtmloptstxtreturn$ifwriterHtml5optsthenH5.figure$mconcat[nlopts,img,capt,nlopts]elseH.div!A.class_"figure"$mconcat[nlopts,img,capt,nlopts]blockToHtmlopts(Paralst)=docontents<-inlineListToHtmloptslstreturn$H.pcontentsblockToHtmlopts(Divattr@(_,classes,_)bs)=docontents<-blockListToHtmloptsbsletcontents'=nlopts>>contents>>nloptsreturn$if"notes"`elem`classesthencasewriterSlideVariantoptsofRevealJsSlides->addAttrsoptsattr$H5.aside$contents'NoSlides->addAttrsoptsattr$H.div$contents'_->memptyelseaddAttrsoptsattr$H.div$contents'blockToHtml_(RawBlockfstr)|f==Format"html"=return$preEscapedStringstr|otherwise=returnmemptyblockToHtmlopts(HorizontalRule)=return$ifwriterHtml5optsthenH5.hrelseH.hrblockToHtmlopts(CodeBlock(id',classes,keyvals)rawCode)=dolettolhs=isEnabledExt_literate_haskellopts&&any(\c->maptoLowerc=="haskell")classes&&any(\c->maptoLowerc=="literate")classesclasses'=iftolhsthenmap(\c->ifmaptoLowerc=="haskell"then"literatehaskell"elsec)classeselseclassesadjCode=iftolhsthenunlines.map("> "++).lines$rawCodeelserawCodehlCode=ifwriterHighlightopts-- check highlighting optionsthenhighlightformatHtmlBlock(id',classes',keyvals)adjCodeelseNothingcasehlCodeofNothing->return$addAttrsopts(id',classes,keyvals)$H.pre$H.code$toHtmladjCodeJusth->modify(\st->st{stHighlighting=True})>>return(addAttrsopts(id',[],keyvals)h)blockToHtmlopts(BlockQuoteblocks)=-- in S5, treat list in blockquote specially-- if default is incremental, make it nonincremental;-- otherwise incrementalifwriterSlideVariantopts/=NoSlidesthenletinc=not(writerIncrementalopts)incaseblocksof[BulletListlst]->blockToHtml(opts{writerIncremental=inc})(BulletListlst)[OrderedListattribslst]->blockToHtml(opts{writerIncremental=inc})(OrderedListattribslst)[DefinitionListlst]->blockToHtml(opts{writerIncremental=inc})(DefinitionListlst)_->docontents<-blockListToHtmloptsblocksreturn$H.blockquote$nlopts>>contents>>nloptselsedocontents<-blockListToHtmloptsblocksreturn$H.blockquote$nlopts>>contents>>nloptsblockToHtmlopts(Headerlevel(_,_,_)lst)=docontents<-inlineListToHtmloptslstsecnum<-liftMstSecNumgetletcontents'=ifwriterNumberSectionsopts&&not(nullsecnum)then(H.span!A.class_"header-section-number"$toHtml$showSecNumsecnum)>>strToHtml" ">>contentselsecontentsreturn$caselevelof1->H.h1contents'2->H.h2contents'3->H.h3contents'4->H.h4contents'5->H.h5contents'6->H.h6contents'_->H.pcontents'blockToHtmlopts(BulletListlst)=docontents<-mapM(blockListToHtmlopts)lstreturn$unordListoptscontentsblockToHtmlopts(OrderedList(startnum,numstyle,_)lst)=docontents<-mapM(blockListToHtmlopts)lstletnumstyle'=camelCaseToHyphenated$shownumstyleletattribs=(ifstartnum/=1then[A.start$toValuestartnum]else[])++(ifnumstyle/=DefaultStylethenifwriterHtml5optsthen[A.type_$casenumstyleofDecimal->"1"LowerAlpha->"a"UpperAlpha->"A"LowerRoman->"i"UpperRoman->"I"_->"1"]else[A.style$toValue$"list-style-type: "++numstyle']else[])return$foldl(!)(ordListoptscontents)attribsblockToHtmlopts(DefinitionListlst)=docontents<-mapM(\(term,defs)->doterm'<-ifnulltermthenreturnmemptyelseliftMH.dt$inlineListToHtmloptstermdefs'<-mapM((liftM(\x->H.dd$(x>>nlopts))).blockListToHtmlopts)defsreturn$mconcat$nlopts:term':nlopts:intersperse(nlopts)defs')lstreturn$defListoptscontentsblockToHtmlopts(Tablecaptalignswidthsheadersrows')=docaptionDoc<-ifnullcaptthenreturnmemptyelsedocs<-inlineListToHtmloptscaptreturn$H.captioncs>>nloptsletpercentw=show(truncate(100*w)::Integer)++"%"letcoltags=ifall(==0.0)widthsthenmemptyelsemconcat$map(\w->ifwriterHtml5optsthenH.col!A.style(toValue$"width: "++percentw)elseH.col!A.width(toValue$percentw)>>nlopts)widthshead'<-ifallnullheadersthenreturnmemptyelsedocontents<-tableRowToHtmloptsaligns0headersreturn$H.thead(nlopts>>contents)>>nloptsbody'<-liftM(\x->H.tbody(nlopts>>mconcatx))$zipWithM(tableRowToHtmloptsaligns)[1..]rows'return$H.table$nlopts>>captionDoc>>coltags>>head'>>body'>>nloptstableRowToHtml::WriterOptions->[Alignment]->Int->[[Block]]->StateWriterStateHtmltableRowToHtmloptsalignsrownumcols'=doletmkcell=ifrownum==0thenH.thelseH.tdletrowclass=caserownumof0->"header"x|x`rem`2==1->"odd"_->"even"cols''<-sequence$zipWith(\alignmentitem->tableItemToHtmloptsmkcellalignmentitem)alignscols'return$(H.tr!A.class_rowclass$nlopts>>mconcatcols'')>>nloptsalignmentToString::Alignment->[Char]alignmentToStringalignment=casealignmentofAlignLeft->"left"AlignRight->"right"AlignCenter->"center"AlignDefault->"left"tableItemToHtml::WriterOptions->(Html->Html)->Alignment->[Block]->StateWriterStateHtmltableItemToHtmloptstag'align'item=docontents<-blockListToHtmloptsitemletalignStr=alignmentToStringalign'letattribs=ifwriterHtml5optsthenA.style(toValue$"text-align: "++alignStr++";")elseA.align(toValuealignStr)return$(tag'!attribs$contents)>>nloptstoListItems::WriterOptions->[Html]->[Html]toListItemsoptsitems=map(toListItemopts)items++[nlopts]toListItem::WriterOptions->Html->HtmltoListItemoptsitem=nlopts>>H.liitemblockListToHtml::WriterOptions->[Block]->StateWriterStateHtmlblockListToHtmloptslst=fmap(mconcat.intersperse(nlopts))$mapM(blockToHtmlopts)lst-- | Convert list of Pandoc inline elements to HTML.inlineListToHtml::WriterOptions->[Inline]->StateWriterStateHtmlinlineListToHtmloptslst=mapM(inlineToHtmlopts)lst>>=return.mconcat-- | Convert Pandoc inline element to HTML.inlineToHtml::WriterOptions->Inline->StateWriterStateHtmlinlineToHtmloptsinline=caseinlineof(Strstr)->return$strToHtmlstr(Space)->return$strToHtml" "(LineBreak)->return$ifwriterHtml5optsthenH5.brelseH.br(Span(id',classes,kvs)ils)->inlineListToHtmloptsils>>=return.addAttrsoptsattr'.H.spanwhereattr'=(id',classes',kvs')classes'=filter(`notElem`["csl-no-emph","csl-no-strong","csl-no-smallcaps"])classeskvs'=ifnullstylesthenkvselse(("style",concatstyles):kvs)styles=["font-style:normal;"|"csl-no-emph"`elem`classes]++["font-weight:normal;"|"csl-no-strong"`elem`classes]++["font-variant:normal;"|"csl-no-smallcaps"`elem`classes](Emphlst)->inlineListToHtmloptslst>>=return.H.em(Stronglst)->inlineListToHtmloptslst>>=return.H.strong(Codeattrstr)->casehlCodeofNothing->return$addAttrsoptsattr$H.code$strToHtmlstrJusth->domodify$\st->st{stHighlighting=True}return$addAttrsopts(id',[],keyvals)hwhere(id',_,keyvals)=attrhlCode=ifwriterHighlightoptsthenhighlightformatHtmlInlineattrstrelseNothing(Strikeoutlst)->inlineListToHtmloptslst>>=return.H.del(SmallCapslst)->inlineListToHtmloptslst>>=return.(H.span!A.style"font-variant: small-caps;")(Superscriptlst)->inlineListToHtmloptslst>>=return.H.sup(Subscriptlst)->inlineListToHtmloptslst>>=return.H.sub(QuotedquoteTypelst)->let(leftQuote,rightQuote)=casequoteTypeofSingleQuote->(strToHtml"‘",strToHtml"’")DoubleQuote->(strToHtml"“",strToHtml"”")inifwriterHtmlQTagsoptsthendomodify$\st->st{stQuotes=True}H.q`fmap`inlineListToHtmloptslstelse(\x->leftQuote>>x>>rightQuote)`fmap`inlineListToHtmloptslst(Mathtstr)->modify(\st->st{stMath=True})>>(casewriterHTMLMathMethodoptsofLaTeXMathML_->-- putting LaTeXMathML in container with class "LaTeX" prevents-- non-math elements on the page from being treated as math by-- the javascriptreturn$H.span!A.class_"LaTeX"$casetofInlineMath->toHtml("$"++str++"$")DisplayMath->toHtml("$$"++str++"$$")JsMath_->doletm=preEscapedStringstrreturn$casetofInlineMath->H.span!A.class_"math"$mDisplayMath->H.div!A.class_"math"$mWebTeXurl->doletimtag=ifwriterHtml5optsthenH5.imgelseH.imgletm=imtag!A.style"vertical-align:middle"!A.src(toValue$url++urlEncodestr)!A.alt(toValuestr)!A.title(toValuestr)letbrtag=ifwriterHtml5optsthenH5.brelseH.brreturn$casetofInlineMath->mDisplayMath->brtag>>m>>brtagGladTeX->return$casetofInlineMath->preEscapedString$"<EQ ENV=\"math\">"++str++"</EQ>"DisplayMath->preEscapedString$"<EQ ENV=\"displaymath\">"++str++"</EQ>"MathML_->doletdt=ift==InlineMaththenDisplayInlineelseDisplayBlockletconf=useShortEmptyTags(constFalse)defaultConfigPPcasetexMathToMathMLdtstrofRightr->return$preEscapedString$ppcElementconfrLeft_->inlineListToHtmlopts(readTeXMath'tstr)>>=return.(H.span!A.class_"math")MathJax_->return$H.span!A.class_"math"$toHtml$casetofInlineMath->"\\("++str++"\\)"DisplayMath->"\\["++str++"\\]"PlainMath->dox<-inlineListToHtmlopts(readTeXMath'tstr)letm=H.span!A.class_"math"$xletbrtag=ifwriterHtml5optsthenH5.brelseH.brreturn$casetofInlineMath->mDisplayMath->brtag>>m>>brtag)(RawInlinefstr)|f==Format"latex"->casewriterHTMLMathMethodoptsofLaTeXMathML_->domodify(\st->st{stMath=True})return$toHtmlstr_->returnmempty|f==Format"html"->return$preEscapedStringstr|otherwise->returnmempty(Link[Strstr](s,_))|"mailto:"`isPrefixOf`s&&s==escapeURI("mailto"++str)->-- autolinkreturn$obfuscateLinkoptsstrs(Linktxt(s,_))|"mailto:"`isPrefixOf`s->dolinkText<-inlineListToHtmloptstxtreturn$obfuscateLinkopts(renderHtmllinkText)s(Linktxt(s,tit))->dolinkText<-inlineListToHtmloptstxtlets'=casesof'#':xs|writerSlideVariantopts==RevealJsSlides->'#':'/':xs_->sletlink=H.a!A.href(toValues')$linkTextreturn$ifnulltitthenlinkelselink!A.title(toValuetit)(Imagetxt(s,tit))|treatAsImages->doletalternate'=stringifytxtletattributes=[A.src$toValues]++(ifnulltitthen[]else[A.title$toValuetit])++ifnulltxtthen[]else[A.alt$toValuealternate']lettag=ifwriterHtml5optsthenH5.imgelseH.imgreturn$foldl(!)tagattributes-- note: null title included, as in Markdown.pl(Image_(s,tit))->doletattributes=[A.src$toValues]++(ifnulltitthen[]else[A.title$toValuetit])return$foldl(!)H5.embedattributes-- note: null title included, as in Markdown.pl(Notecontents)|writerIgnoreNotesopts->returnmempty|otherwise->dost<-getletnotes=stNotesstletnumber=(lengthnotes)+1letref=shownumberhtmlContents<-blockListToNoteoptsrefcontents-- push contents onto front of notesput$st{stNotes=(htmlContents:notes)}letrevealSlash=['/'|writerSlideVariantopts==RevealJsSlides]letlink=H.a!A.href(toValue$"#"++revealSlash++writerIdentifierPrefixopts++"fn"++ref)!A.class_"footnoteRef"!prefixedIdopts("fnref"++ref)$H.sup$toHtmlrefreturn$casewriterEpubVersionoptsofJustEPUB3->link!customAttribute"epub:type""noteref"_->link(Citecitsil)->docontents<-inlineListToHtmloptsilletcitationIds=unwords$mapcitationIdcitsletresult=H.span!A.class_"citation"$contentsreturn$ifwriterHtml5optsthenresult!customAttribute"data-cites"(toValuecitationIds)elseresultblockListToNote::WriterOptions->String->[Block]->StateWriterStateHtmlblockListToNoteoptsrefblocks=-- If last block is Para or Plain, include the backlink at the end of-- that block. Otherwise, insert a new Plain block with the backlink.letbacklink=[Link[Str"↩"]("#"++writerIdentifierPrefixopts++"fnref"++ref,[])]blocks'=ifnullblocksthen[]elseletlastBlock=lastblocksotherBlocks=initblocksincaselastBlockof(Paralst)->otherBlocks++[Para(lst++backlink)](Plainlst)->otherBlocks++[Plain(lst++backlink)]_->otherBlocks++[lastBlock,Plainbacklink]indocontents<-blockListToHtmloptsblocks'letnoteItem=H.li!(prefixedIdopts("fn"++ref))$contentsletnoteItem'=casewriterEpubVersionoptsofJustEPUB3->noteItem!customAttribute"epub:type""footnote"_->noteItemreturn$nlopts>>noteItem'