zDoc 假想你的工作目录由如下结构组成
[imgs] # 存放你所有的图片文件,zDoc 会全部 copy 到目标目录
[js] # 存放你所有的脚本文件,zDoc 会全部 copy 到目标目录
[css] # 存放你所有的样式表单,zDoc 会全部 copy 到目标目录
[_tmpl] # 这里存放模板,一个网站可以有多个模板
[_libs] # 这里存放代码片段
zdoc.conf # 你的网站项目的总体配置文件
index.xml # 【选】你的文档索引的顺序
index.html # 所有的 HTML,markdown,zDoc 文件都会一视同仁进行转换
readme.md
aboutus.zdoc
_tmpl 目录这个目录就是给 ZDocTemplateFactory 的实现类使用的模板目录。
具体如何获取模板,由实现类决定,下面我就用默认提供的 FreemarkerTemplateFactory
来作为例子:
[_tmpl] # 目录内任何一个 .ftl 文件就是一个Freemarker模板
normal.ftl # 这个模板的名字为 "normal"
[black]
myblank.ftl # 这个模板的名字为 "black.myblank"
无论你是什么模板引擎,你都会得到一个用 org.nutz.lang.util.NutMap 封装的一个上下文:
{
siteTitle : "雨打沙滩点点坑", // 整个站点的标题
topTags : [{key:'34..a9',text:'翻译',count:87},{...}],
othersTag : {key:'others',text:'未归类',count:194},
tags : [{key:'ae..f0',text:'小说',count:87},{...}],
tagPath : 'tags', // 要将所有的 tag 索引页输出到哪个路径
// 每个页面模板将额外得到这个属性
// 如果对应 tag 页面,只有 title, 和 bpath 有效
doc : {
author : [{name:'zozoh',email:'zozoh@me.com'},{...}],
verifier : [{name:'zozoh',email:'zozoh@me.com'},{...}],
title : '文档的标题',
tags : ['标签A', '标签B', '标签C'],
lm : Date(2014-12-09 12:34:21),
rpath : 'post/2014/001.md', // 相对于根的路径
bpath : '../..', // 回到根的路径
conent : '<div>.....</div>', // 渲染出来的 HTML
... 剩下的是你在 doc 声明的对应属性 ...
}
// 每个标签列表页将额外得到这个属性
tag : {
key : '45..f2',
text : '翻译',
count : 53,
items : {
// @see ZDocIndex.toMap()
}
}
}
因此在任何模板引擎里,如果要输出标题,应该支持类似如下写法:
...
<title>${doc.title}</title>
...
_libs 目录和 _tmpl 目录基本一样 ...
放置这个文件的的目录将作为 zdoc 的工作目录,这个文件 ...
世间任何文档,都是相似的
抽象的看,任何一个文档都可以下列结构来描述
文档级属性 { # ZDocMetas
标题
作者
子标题
创建日期
指定样式表
…
}
标题 # ZDocNode.depth=0
… 一块内容 … # ZDocNode.depth=-1
标题 A
… 一块内容 …
… 一块内容 …
我们可以看出,所谓的 zDoc 文档结构,就是一个 ZDocNode 组成的文档结构树。所有的叶子节点 是段落内容,所有的中间节点则作为标题
任何一个节点, ...
对于每个段落的解析,依靠自动机。本文为自动机的实现者提供一些参考
头 .................... 尾
[C][C][C] # 字符缓冲栈
[T][T][T] # 数据栈
[@][@] # 操作栈
1 ']' # 指示堆栈,并联自动机当做退出字符,串联当做子机下标
四个堆栈的操作原则是:“谁压入谁弹出”
堆栈通过 {..} 来表示,可以构成级联
{..}
{..}
{..}
{..}
{..}
每个自动机都有方法
enter(AmStack, char):AmStatus # 试图进入这个自动机,如果成功将设置堆栈
eat(AmStack, char):AmStatus # 消费字符
done(AmStack):void # 将字符缓冲的内容填充到数据栈
任何一个自动机每次被执行都会返回如下四个行为之一
DROP # 丢弃当前堆栈
CONTINUE # 继续,读取下一个字符,执行栈顶自动机的 run 方法
DONE # 将弹出操作栈顶自动机,并执行它的 done 方法
DONE_BACK # 执行 DONE 操作,并重新进入下一个可能的自动机
一个堆栈的数据结构
buffer # 字符缓冲
objs # 数据栈
ams # 操作栈
qcs # 退出字符
sis # 指示堆栈
i_obj
i_am
i_qc
i_si
candidates # 候选堆栈
它应该支持的操作
enter(Am, char): bool # 是否可以让这个自动机进入堆栈
eat(char) : AmStatus # 消费字符,返回 false 表示不能消费了
done : void # 调用栈顶自动机的 done
close : T # 将对象堆栈清空
pushAm(Am)
popAm : Am
peekAm : Am
pushObj(T)
popObj : T
peekObj : T
pushQc(char)
popQc : char
qc : char
pushSi(int)
popSi : int
si : int
串联自动机有一个固定的进入字符
假设一个堆栈状态为:
[] # 字符缓冲是干净的
[] ... # 数据栈
[] ... # 操作栈
... # 指示堆栈
enter ...
#------------------------------------------------
如果符合进入字符
[] # 字符缓冲内容由第一个子机决定
[T] ... # 准备一个对象
[&] ... # 压入自己
-1 ... # 指示字符为自己第一个子机
eat ...
#------------------------------------------------
如果发现当前子机下标小于 0,则表示子机未被执行 enter ,那么就
将下标变成正数,并调用对应子机的 enter
(注意,这里的下标是 1 base,需要转换成 0 base 使用)
[] #
[T] ... # 还是那个对象
[&] ... # 串联自动机
1 ... # 下标变成正数,并调用子机的 enter
如果当前子机 enter 未遂,或者 eat 返回 DROP 了,那么自己也返回 DROP
如果返回的状态是 DONE 或者 DONE_BACK,
会调用当前子机的 done,并试图切换到下一个子机
[] #
[T] ... # 还是那个对象
[&] ... # 串联自动机
-2 ... # 下标指向下一个子机,并调用子机的 enter
如果没有下一个子机了,则返回 DONE | DONE_BACK
done ...
#------------------------------------------------
如果为 done 时,堆栈应该为
[C][C]... # 缓冲可能为空也可能有字符
[T] ... # 还是那个对象
[&] ... # 操作栈顶应该是最后一个子
2 ... # 下标指向最后一个子机
如果没有达到最后一个子机,那么调用当前子机的 done
然后将将自己退栈
[]
[] ... # 将 T 组合到之前的对象中
[] ... # 清除自己
... # 清除了指示下标
如果发现有超过一个自动机都进入了堆栈,并联自动机会依次为其构建堆栈
假设一个堆栈状态为:
[] # 字符缓冲是干净的
[] ... # 数据栈
[] ... # 操作栈
... # 指示堆栈
enter ...
#------------------------------------------------
构建新堆栈:
[]
[] # 不要准备对象
[@] # 表示有子自动机进入了
']' # 自己的退出字符
如果超过一个自动机进入了,那么将堆栈变成
{候选堆栈A} {候选堆栈B} -?- {母堆栈}
{候选堆栈C} /
...
那么就会将自身压入母堆栈,同时也要在母堆栈标识退出字符
[]
[] ...
[+]... # 仅仅在当前堆栈压入自身
']'... # 压入自己的退出字符
eat ...
#------------------------------------------------
如果没有候选堆栈,则本自动机将执行选择一个候选堆栈
如果有候选堆栈,那么它的状态应该为 :
[]
[] ...
[+]...
']'...
[?] # 子机设置
[?] # 子机设置
[@] # 某个子机
']' # 自己的退出字符
每次 eat 如果某个候选堆栈 DROP , 就移除掉它
保证每次都给所有的候选堆栈消费字符,如果某个堆栈 DONE 了,
则将这个堆栈关闭后得到对象组合到母堆栈中。
done ...
#------------------------------------------------
如果没有候选堆栈,那么就什么也不做
如果有多个候选堆栈,并联自动机会首先调用候选A的栈顶自动机的 done
{候选堆栈A}.close() => T
{self}.mergeHead(T)
{self}.pushQc({候选堆栈A}.popQc())
并将其压入自己所在堆栈,否则,就会调用自己堆栈栈顶自动机的 done,
让自己的的堆栈状态为:
[] # 字符缓冲
[] ... # 这个是自己的对象
[+] ... # 头部就只有自己
']' ... # 自己的退出字符在顶部
执行堆栈的真正弹出
[]
[] ... # 将 T 组合到之前的对象中
[] ... # 清除了自动机
... # 清除了退出字符
一个 zDoc 文档集合可以被输出成为任何介质
在 zDoc 的观点里,一次仅仅渲染一个文档是不够的。通常你会写很多文档, 然后放到一个文件夹下面,我们在 里声明了一个 zDoc工作目录的约定。 你解析出来这个目录的结构大约参见如下的对象结构
public class ZDocHome {
protected ZDir src;
protected ZCache<ZDocHtmlCacheItem> libs;
protected ZCache<ZDocHtmlCacheItem> tmpl;
protected List<ZDir> rss;
protected Context vars;
protected List<ZDocRule> rules;
protected ZDocIndex index;
...
你的渲染将面对整个 ZDocHome
大小写敏感
| Name | 说明 |
|---|---|
| doc.content | 文档被渲染过后的内容 |
| doc.k ... | |