// // Copyright (c) 2007, Brian Frank and Andy Frank // Licensed under the Academic Free License version 3.0 // // History: // 17 Feb 07 Brian Frank Creation // ** ** HtmlDocWriter outputs a fandoc model to XHTML ** ** See [pod doc]`pod-doc#api` for usage. ** @Js class HtmlDocWriter : DocWriter { ////////////////////////////////////////////////////////////////////////// // Constructor ////////////////////////////////////////////////////////////////////////// new make(OutStream out := Env.cur.out) { this.out = out } ////////////////////////////////////////////////////////////////////////// // Config ////////////////////////////////////////////////////////////////////////// ** Callback to perform link resolution and checking for ** every Link element |Link link|? onLink := null ** Callback to perform image link resolution and checking |Image img|? onImage := null ////////////////////////////////////////////////////////////////////////// // DocWriter ////////////////////////////////////////////////////////////////////////// override Void docStart(Doc doc) { out.print("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\"\n") out.print(" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n") out.print("<html>\n") out.print("<head>\n") docHead(doc) out.print("</head>\n") } virtual Void docHead(Doc doc) { out.print(" <meta http-equiv='Content-Type' content='text/html; charset=UTF-8'/>\n") doc.meta.each |Str val, Str key| { switch (key) { case "title": out.print(" <title>") safeText(val) out.print("</title>\n") default: out.print(" <meta") attr("name", key) attr("content", val) out.print("/>\n") } } } override Void docEnd(Doc doc) { out.print("</html>") } override Void elemStart(DocElem elem) { if (elem.isBlock) out.writeChar('\n') // if hyperlink to code, then wrap in code element if (elem.id == DocNodeId.link) { if (onLink != null) onLink(elem) if (elem->isCode) out.print("<code>") } out.writeChar('<').print(elem.htmlName) if (elem.anchorId != null) out.print(" id='$elem.anchorId'") switch (elem.id) { case DocNodeId.link: link := elem as Link out.print(" href='$link.uri.toXml'") case DocNodeId.image: img := elem as Image if (onImage != null) onImage(img) out.print(" src='$img.uri.toXml' alt='") safeAttr(img.alt) out.print("'") if (img.size != null) { toks := img.size.split('x') w := toks.getSafe(0) h := toks.getSafe(1) if (w != null) out.print(" width='").print(w).print("'") if (h != null) out.print(" height='").print(h).print("'") } out.print("/>") return case DocNodeId.para: para := elem as Para if (para.admonition != null) { out.print(" class='$para.admonition'") out.print(">").print(para.admonition).print(": ") return } case DocNodeId.orderedList: ol := elem as OrderedList out.print(" style='list-style-type:$ol.style.htmlType'") case DocNodeId.hr: out.printLine("/>") return } out.writeChar('>') } override Void elemEnd(DocElem elem) { if (elem.id == DocNodeId.image) return if (elem.id == DocNodeId.hr) return out.writeChar('<').writeChar('/').print(elem.htmlName).writeChar('>') // if hyperlink to code, then wrap in code element if (elem.id == DocNodeId.link && ((Link)elem).isCode) out.print("</code>") if (elem.isBlock) out.writeChar('\n') } override Void text(DocText text) { safeText(text.str) } ////////////////////////////////////////////////////////////////////////// // Escapes ////////////////////////////////////////////////////////////////////////// private Void attr(Str name, Str val) { out.writeChar(' ').print(name).print("='") safeAttr(val) out.writeChar('\'') } private Void safeAttr(Str s) { s.each |Int ch| { if (ch == '<') out.print("<") else if (ch == '&') out.print("&") else if (ch == '\'') out.print("'") else if (ch == '"') out.print(""") else out.writeChar(ch) } } private Void safeText(Str s) { s.each |Int ch| { if (ch == '<') out.print("<") else if (ch == '&') out.print("&") else out.writeChar(ch) } } ////////////////////////////////////////////////////////////////////////// // Fields ////////////////////////////////////////////////////////////////////////// OutStream out }