//
// Copyright (c) 2011, Brian Frank and Andy Frank
// Licensed under the Academic Free License version 3.0
//
// History:
// 13 Aug 11 Brian Frank Creation
//
using fandoc
using fandoc::Doc as FandocDoc
using markdown::Xetodoc
using markdown::LinkResolver
using web
**
** DocRenderer is base class for rendering a Doc.
** See `writeDoc` for rendering pipeline.
**
abstract class DocRenderer
{
//////////////////////////////////////////////////////////////////////////
// Constructor
//////////////////////////////////////////////////////////////////////////
** All subclasses must implement ctor with env, out, doc params.
new make(DocEnv env, WebOutStream out, Doc doc)
{
this.envRef = env
this.outRef = out
this.docRef = doc
}
//////////////////////////////////////////////////////////////////////////
// State
//////////////////////////////////////////////////////////////////////////
** Environment with access to model, theme, linking, etc
virtual DocEnv env() { envRef }
private DocEnv envRef
** HTML output stream
virtual WebOutStream out() { outRef }
private WebOutStream outRef
** Document to be renderered
virtual Doc doc() { docRef }
private Doc docRef
** Theme to use for rendering chrome and navigation.
** This field is initialized from `DocEnv.theme`.
virtual DocTheme theme() { env.theme }
//////////////////////////////////////////////////////////////////////////
// Hooks
//////////////////////////////////////////////////////////////////////////
**
** Render the `doc`. This method delegates to:
** 1. `DocTheme.writeStart`
** 2. `DocTheme.writeBreadcrumb`
** 3. `writeContent`
** 3. `DocTheme.writeEnd`
**
virtual Void writeDoc()
{
theme.writeStart(this)
theme.writeBreadcrumb(this)
writeContent
theme.writeEnd(this)
}
**
** Subclass hook to render document specific content.
** See `writeDoc` for rendering pipeline.
**
abstract Void writeContent()
//////////////////////////////////////////////////////////////////////////
// Utils
//////////////////////////////////////////////////////////////////////////
**
** Write an '<a>' element for the given link from this renderer
** document to another document. See `DocEnv.linkUri`.
**
virtual Void writeLink(DocLink link)
{
out.a(env.linkUri(link)).esc(link.dis).aEnd
}
**
** Convenience for 'writeLink(linkTo(target, dis, frag))'
**
virtual Void writeLinkTo(Doc target, Str? dis := null, Str? frag := null)
{
if (dis == null) dis = target is DocChapter ? target.title : target.docName
writeLink(linkTo(target, dis, frag))
}
**
** Create a DocLink from this renderer doc to the target document.
**
DocLink linkTo(Doc target, Str? dis := null, Str? frag := null)
{
if (dis == null) dis = target is DocChapter ? target.title : target.docName
return DocLink(this.doc, target, dis, frag)
}
**
** Write the given markdown/fandoc string as HTML. This method
** delegates to `DocEnv.link` and `DocEnv.linkUri` to
** resolve links from the current document.
**
virtual Void writeFandoc(DocFandoc doc, DocFormat format)
{
if (format === DocFormat.markdown)
writeMarkdownFormat(doc)
else
writeFandocFormat(doc)
}
** Hook for resolving both markdown and fandoc links
private DocLink? resolveLink(Str orig, DocLoc loc)
{
link := env.link(doc, orig, false)
if (link == null)
{
env.err("Broken link: $orig", loc)
}
else
{
env.linkCheck(link, loc)
}
return link
}
//////////////////////////////////////////////////////////////////////////
// Markdown
//////////////////////////////////////////////////////////////////////////
** Choke point for writing DocFormat as markdown
private Void writeMarkdownFormat(DocFandoc doc)
{
xetodoc := Xetodoc().withLinkResolver(DocMarkdownLinker(this))
curDocLoc = doc.loc
out.w(xetodoc.toHtml(doc.text))
curDocLoc = null
}
** Markdown handling for link nodes
internal Void onMarkdownLink(markdown::Link node)
{
orig := node.destination
loc := DocLoc(curDocLoc.file, curDocLoc.line + node.loc.line - 1)
link := resolveLink(orig, loc)
if (link != null)
{
node.destination = env.linkUri(link).encode
if (node.shortcut) node.setText(link.dis)
}
}
private DocLoc? curDocLoc
//////////////////////////////////////////////////////////////////////////
// Fandoc
//////////////////////////////////////////////////////////////////////////
** Choke point for writing DocFormat as fandoc
private Void writeFandocFormat(DocFandoc doc)
{
// parse fandoc
docLoc := doc.loc
parser := FandocParser()
parser.silent = true
root := parser.parse(docLoc.file, doc.text.in)
// if no errors, then write as HTML
if (parser.errs.isEmpty)
{
writer := env.initFandocHtmlWriter(out)
writer.onLink = |Link elem| { onFandocLink(elem, toFandocElemLoc(docLoc, elem.line)) }
writer.onImage = |Image elem| { onFandocImage(elem, toFandocElemLoc(docLoc, elem.line)) }
root.children.each |child| { child.write(writer) }
}
// otherwise report errors and print as <pre>
else
{
// report each error
parser.errs.each |err|
{
env.err(err.msg, toFandocElemLoc(docLoc, err.line))
}
// print as <pre>
out.pre.w(doc.text).preEnd
}
}
** Map document location and element to the element location
private DocLoc toFandocElemLoc(DocLoc docLoc, Int line)
{
DocLoc(docLoc.file, docLoc.line + line - 1)
}
** Fandoc handling for link nodes
@NoDoc
virtual Void onFandocLink(Link elem, DocLoc loc)
{
// route to DocEnv.link
orig := elem.uri
link := resolveLink(orig, loc)
if (link == null) return
/// get environment URI for the DocLink
elem.uri = env.linkUri(link).encode
elem.isCode = link.target.isCode
// if link text was original URI, then update with DocLink.dis
if (elem.children.first is DocText && elem.children.first.toStr == orig)
elem.removeAll.add(DocText(link.dis))
}
** Fandoc handling for inage nodes
@NoDoc
virtual Void onFandocImage(Image elem, DocLoc loc)
{
}
}
**************************************************************************
** DocMarkdownLinker
**************************************************************************
internal class DocMarkdownLinker : LinkResolver
{
new make(DocRenderer r) { this.r = r }
private DocRenderer r
override Void resolve(markdown::LinkNode node)
{
if (node is markdown::Link) r.onMarkdownLink(node)
}
}