//
// 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)
}
}
@NoDoc Void safeText(Str s)
{
s.each |Int ch|
{
if (ch == '<') out.print("<")
else if (ch == '&') out.print("&")
else out.writeChar(ch)
}
}
//////////////////////////////////////////////////////////////////////////
// Fields
//////////////////////////////////////////////////////////////////////////
OutStream out
}