Apache Anakia 快速入门与集成示例

Apache Anakia 快速入门实战:从零创建 XML 源文档与 site.vsl 模板,build.xml 注册 AnakiaTask 跑通 XML 转 HTML 并验证产物,含一键脚本与 systemd 定时构建集成,全程实测。

Apache AnakiaAnakia2026/10/13

Apache Anakia 快速入门与集成示例

目标:从零搭建一个可运行的 Anakia 文档站点工程——创建 XML 源文档与 navigation 导航、编写 Velocity 模板(site.vsl)与引擎属性(velocity.properties)、在 build.xml 中注册并配置 AnakiaTask,完成首次 XML→HTML 转换并逐项验证产物(UTF-8 中文、导航菜单、实体转义、构建时间戳),最后给出「一键构建脚本 + systemd 定时构建」两个集成示例,全部步骤在 Ubuntu 22.04(OpenJDK 1.8.0_504、Ant 1.10.18、Velocity 1.7)实测通过。

一、环境信息

本篇所有命令与回显均在以下环境中实测采集:

  • 操作系统:Ubuntu 22.04.5 LTS,x86_64
  • JDK:OpenJDK 1.8.0_504(/usr/lib/jvm/java-8-openjdk-amd64)
  • 构建工具:Apache Ant 1.10.18(ANT_HOME=/opt/apache-ant-1.10.18)
  • 组件版本:Apache Anakia(org.apache.velocity:velocity:1.7 及其依赖四 jar 位于 /opt/anakia-lib/)
  • 演示工程:/opt/anakia-quick(本篇从零创建)
  • 执行用户:root;演示账号 admin / q1w2e3r4(本篇为本地构建操作,如涉及远程登录使用该账号)

💡 技术拓展:Anakia 的名字取自「Apache + ia」且 style 属性名带有历史色彩——它对标的是 XSLT 转换,但实际使用的不是 XSL 样式表,而是 Velocity 模板(.vsl):XML 由 JDOM 解析成元素树交给 Velocity 上下文($root),输出格式完全由 VTL 控制。因此「XML+XSL 转 HTML」在 Anakia 里应理解为「XML + Velocity 样式模板转 HTML」,不需要任何 XSLT 处理器。

二、前置条件

Anakia 环境就位(JDK + Ant + 四个运行时 jar)是本篇前提,安装过程见《Ubuntu 22.04 安装 Apache Anakia 指南》。核验命令与实测回显:

which ant && ant -version

回显(实测):

/opt/apache-ant-1.10.18/bin/ant
Apache Ant(TM) version 1.10.18 compiled on September 3 2026
ls /opt/anakia-lib/

回显(实测):

antlr-2.7.2.jar
commons-collections-3.2.2.jar
commons-lang-2.6.jar
jdom-1.1.3.jar
saxpath-1.0-FCS.jar
velocity-1.7.jar
werken-xpath-0.9.4.jar

(velocity-1.7.jar + commons-lang + commons-collections + jdom 四个是 Anakia 运行必需;antlr/saxpath/werken-xpath 三个仅在模板使用 $xpath 时需要,见《配置参数详解》第六章。)

三、Anakia 工作原理与工程结构

Anakia 的一次构建分三步:① JDOM SAXBuilder 解析 basedir 下每个 .xml 源文件;② 把根元素、导航文档、工具对象注入 Velocity 上下文,用 style 指定的 .vsl 模板渲染;③ 输出按 extension 命名写入 destdir。它无常驻进程、无端口,每次 ant 都是一个全新 JVM 完成全部转换后退出。

本篇演示工程的目录结构(转换前):

anakia-quick/
├── build.xml                    # Ant 构建文件:注册 AnakiaTask 并配置参数
├── src/xml/
│   ├── index.xml                # 首页源文档
│   ├── guide.xml                # 构建指南页源文档
│   └── navigation.xml           # 站点导航(注入 $project,不进产物)
└── styles/
    ├── site.vsl                 # Velocity 模板(站点外观)
    └── velocity.properties      # 引擎属性(输出/输入编码)

六个文件的内容与写法见第四~六章,全部给出可直接粘贴的完整版本;第七章执行首次构建,第八章做集成。

四、编写 XML 源文档与导航文件

创建目录骨架并写入两个源文档与导航文件。index.xml:

mkdir -p /opt/anakia-quick/src/xml /opt/anakia-quick/styles && cat > /opt/anakia-quick/src/xml/index.xml <<'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<document>
  <title>Anakia 快速入门站点</title>
  <section name="简介">
    <p>本站点由 Apache Anakia 从 XML 源文档自动生成,模板引擎为 Velocity 1.7。</p>
    <p>XML 源文件放在 src/xml 目录,Velocity 模板放在 styles 目录。</p>
  </section>
  <section name="转义演示">
    <p>源文档里写 &amp; 与 &lt;tag&gt; 属于 XML 实体,输出阶段由模板的 escape 工具再次转义。</p>
  </section>
</document>
EOF

guide.xml:

cat > /opt/anakia-quick/src/xml/guide.xml <<'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<document>
  <title>构建指南</title>
  <section name="构建命令">
    <p>进入工程目录执行 ant 即可完成 XML 到 HTML 的转换。</p>
  </section>
  <section name="增量构建">
    <p>Anakia 默认启用 lastModifiedCheck,源文件与模板均未变化时跳过转换。</p>
  </section>
</document>
EOF

navigation.xml(站点名与菜单,模板里通过 $project 读取):

cat > /opt/anakia-quick/src/xml/navigation.xml <<'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<project name="anakia-quick">
  <title>Anakia Quick Site</title>
  <menu>
    <item name="首页" href="index.html"/>
    <item name="构建指南" href="guide.html"/>
  </menu>
</project>
EOF

三个要点:① heredoc 定界符写 <<'EOF'(带单引号)是为了让 $root、#foreach 等 VTL 记号原样落盘,不被 shell 展开——第五、六章同理;② 源文档约定根元素 document、子元素 title/section/p,这是模板侧的解析契约(换成自己的 DTD 时模板同步改);③ 正文中的 & 必须写成 &amp;、< 写成 &lt;——源文件首先是合法 XML(违反时的真实报错见 9.1 节)。

五、编写 Velocity 模板与引擎属性

styles/site.vsl——站点唯一外观定义,覆盖导航渲染、正文遍历、实体转义、构建时间戳四个能力:

cat > /opt/anakia-quick/styles/site.vsl <<'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<html>
  <head>
    <title>$root.getChild("title").getTextTrim()</title>
  </head>
  <body>
    <div id="nav">
#foreach ($item in $project.getChild("menu").getChildren("item"))
      <a href="$item.getAttributeValue('href')">$item.getAttributeValue("name")</a>
#end
    </div>
    <h1>$root.getChild("title").getTextTrim()</h1>
#foreach ($node in $root.getChildren("section"))
    <h2>$node.getAttributeValue("name")</h2>
#foreach ($para in $node.getChildren("p"))
    <p>$escape.getText($para.getTextTrim())</p>
#end
#end
    <hr/>
    <p>Generated by Apache Anakia / Velocity 1.7 at $date</p>
  </body>
</html>
EOF

模板逐行说明($ 与 # 开头均为 VTL 指令/引用,属程序逻辑而非占位符):$root 是当前源文档的 JDOM 根元素,.getChild("title").getTextTrim() 取子元素文本;#foreach ... #end 遍历 getChildren() 返回的元素列表;$project 是 navigation.xml 的 JDOM 文档,菜单由它渲染;$escape 是 Anakia 内置 HTML 转义工具(Escape.getText(String)),防止正文中的 &、< 破坏 HTML 结构;$date 是转换时刻的 java.util.Date。JDOM 方法调用语法见 https://jdom.org/docs/apidocs/1.1.3/ 。

styles/velocity.properties——中文站点的关键两行(缺省时 Velocity 输出编码回退 ISO-8859-1,中文全部变 ?,实测过程见《配置参数详解》5.4 节):

cat > /opt/anakia-quick/styles/velocity.properties <<'EOF'
output.encoding=UTF-8
input.encoding=UTF-8
EOF

六、编写 build.xml:注册并配置 AnakiaTask

build.xml 完整内容(taskdef 把 AnakiaTask 注册为 anakia 标签,docs 目标执行转换,另配 clean 与 rebuild 两个辅助目标):

cat > /opt/anakia-quick/build.xml <<'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<project name="anakia-quick" default="docs" basedir=".">

  <property name="docs.src" location="${basedir}/src/xml"/>
  <property name="docs.dest" location="${basedir}/docs"/>

  <taskdef name="anakia" classname="org.apache.velocity.anakia.AnakiaTask">
    <classpath>
      <pathelement location="/opt/anakia-lib/velocity-1.7.jar"/>
      <pathelement location="/opt/anakia-lib/commons-lang-2.6.jar"/>
      <pathelement location="/opt/anakia-lib/commons-collections-3.2.2.jar"/>
      <pathelement location="/opt/anakia-lib/jdom-1.1.3.jar"/>
    </classpath>
  </taskdef>

  <target name="docs" description="Transform XML docs to HTML with Anakia">
    <mkdir dir="${docs.dest}"/>
    <anakia basedir="${docs.src}" destdir="${docs.dest}"
            extension=".html"
            style="site.vsl"
            templatePath="${basedir}/styles"
            velocityPropertiesFile="${basedir}/styles/velocity.properties"
            projectFile="navigation.xml"
            lastModifiedCheck="true"
            excludes="navigation.xml"/>
  </target>

  <target name="clean" description="Remove generated HTML">
    <delete dir="${docs.dest}"/>
  </target>

  <target name="rebuild" depends="clean,docs" description="Clean then full rebuild"/>
</project>
EOF

逐项说明(<anakia> 十个属性完整语义见《配置参数详解》第四、五章;property 元素里的 ${basedir} 等为 Ant 属性引用,运行时求值,属构建脚本程序逻辑而非占位符):

  • taskdef 的 <classpath> 内嵌四个运行时 jar 的绝对路径——换机器部署只需改这一段;
  • basedir/destdir 指向 src/xml 与 docs(目录自动创建);
  • style="site.vsl" 相对 templatePath 解析,模板放 styles/ 下;
  • velocityPropertiesFile 显式指向 styles/velocity.properties,不依赖「当前目录碰巧有同名文件」;
  • projectFile="navigation.xml" 相对 basedir(即 src/xml/)解析;
  • excludes="navigation.xml" 防止导航文件本身被转成 navigation.html;
  • lastModifiedCheck="true" 保持增量(该属性本就是默认值,写出来是为了自文档化)。

七、首次构建与产物验证

执行首次构建:

cd /opt/anakia-quick && ant

回显(实测):

Buildfile: /opt/anakia-quick/build.xml

docs:
    [mkdir] Created dir: /opt/anakia-quick/docs
   [anakia] Transforming into: /opt/anakia-quick/docs
   [anakia] Input:  index.xml
   [anakia] Output: /opt/anakia-quick/docs/index.html
   [anakia] Input:  guide.xml
   [anakia] Output: /opt/anakia-quick/docs/guide.html

BUILD SUCCESSFUL
Total time: 0 seconds

(navigation.xml 被 excludes 挡住,不出现 Input/Output 行。)验证首页产物完整内容:

cat /opt/anakia-quick/docs/index.html

回显(实测,全文):

<?xml version="1.0" encoding="UTF-8"?>
<html>
  <head>
    <title>Anakia 快速入门站点</title>
  </head>
  <body>
    <div id="nav">
      <a href="index.html">首页</a>
      <a href="guide.html">构建指南</a>
    </div>
    <h1>Anakia 快速入门站点</h1>
    <h2>简介</h2>
    <p>本站点由 Apache Anakia 从 XML 源文档自动生成,模板引擎为 Velocity 1.7。</p>
    <p>XML 源文件放在 src/xml 目录,Velocity 模板放在 styles 目录。</p>
    <h2>转义演示</h2>
    <p>源文档里写 &amp; 与 &lt;tag&gt; 属于 XML 实体,输出阶段由模板的 escape 工具再次转义。</p>
    <hr/>
    <p>Generated by Apache Anakia / Velocity 1.7 at Tue Oct 13 15:37:26 GMT 2026</p>
  </body>
</html>

对照源文档逐项核对:中文全部完整(UTF-8 配置生效);导航两项来自 navigation.xml($project 生效);正文 &amp;/&lt;tag&gt; 输出为 HTML 实体形式($escape 生效,浏览器中显示为 & 与 <tag>);末行 at Tue Oct 13 15:37:26 GMT 2026 为构建时刻($date 生效)。

增量行为验证——只修改 guide.xml 再跑一次,应只重转该文件:

cd /opt/anakia-quick && touch src/xml/guide.xml && ant

回显(实测):

Buildfile: /opt/anakia-quick/build.xml

docs:
   [anakia] Transforming into: /opt/anakia-quick/docs
   [anakia] Input:  guide.xml
   [anakia] Output: /opt/anakia-quick/docs/guide.html

BUILD SUCCESSFUL
Total time: 0 seconds

全量重建用 rebuild 目标(先 clean 删产物目录、再 docs 全转,依赖链清晰可见):

cd /opt/anakia-quick && ant rebuild

回显(实测):

Buildfile: /opt/anakia-quick/build.xml

clean:
   [delete] Deleting directory /opt/anakia-quick/docs

docs:
    [mkdir] Created dir: /opt/anakia-quick/docs
   [anakia] Transforming into: /opt/anakia-quick/docs
   [anakia] Input:  guide.xml
   [anakia] Output: /opt/anakia-quick/docs/guide.html
   [anakia] Input:  index.xml
   [anakia] Output: /opt/anakia-quick/docs/index.html

rebuild:

BUILD SUCCESSFUL
Total time: 0 seconds

ant -p 确认三个目标及默认目标:

cd /opt/anakia-quick && ant -p

回显(实测):

Buildfile: /opt/anakia-quick/build.xml

Main targets:

 clean    Remove generated HTML
 docs     Transform XML docs to HTML with Anakia
 rebuild  Clean then full rebuild
Default target: docs

八、集成示例:一键构建脚本与 systemd 定时构建

8.1 一键构建脚本

把「全量重建」固化为脚本,供手动发布或 CI 调用:

printf "#!/bin/bash\nset -e\ncd /opt/anakia-quick\n/opt/apache-ant-1.10.18/bin/ant rebuild\n" > /opt/anakia-quick/build-site.sh && chmod +x /opt/anakia-quick/build-site.sh

(脚本用绝对路径调用 ant,避免受执行者 PATH 差异影响;set -e 保证 ant 失败时脚本以非零码退出。)实测执行:

/opt/anakia-quick/build-site.sh

回显(实测):

Buildfile: /opt/anakia-quick/build.xml

clean:
   [delete] Deleting directory /opt/anakia-quick/docs

docs:
    [mkdir] Created dir: /opt/anakia-quick/docs
   [anakia] Transforming into: /opt/anakia-quick/docs
   [anakia] Input:  guide.xml
   [anakia] Output: /opt/anakia-quick/docs/guide.html
   [anakia] Input:  index.xml
   [anakia] Output: /opt/anakia-quick/docs/index.html

rebuild:

BUILD SUCCESSFUL
Total time: 0 seconds

8.2 systemd 定时构建

用 systemd timer 实现每夜 02:30 自动重建。先写 service 单元(oneshot 型,执行一次即退出):

printf "[Unit]\nDescription=Anakia nightly site build\n\n[Service]\nType=oneshot\nUser=root\nWorkingDirectory=/opt/anakia-quick\nExecStart=/opt/anakia-quick/build-site.sh\n" > /etc/systemd/system/anakia-build.service

再写 timer 单元(OnCalendar 每日 02:30;Persistent=true 表示停机错过的触发点会在开机后补跑一次):

printf "[Unit]\nDescription=Run Anakia site build nightly\n\n[Timer]\nOnCalendar=*-*-* 02:30:00\nPersistent=true\n\n[Install]\nWantedBy=timers.target\n" > /etc/systemd/system/anakia-build.timer

加载并启用(每条 systemctl 命令只操作一个单元):

systemctl daemon-reload
systemctl enable --now anakia-build.timer

回显(实测):

Created symlink /etc/systemd/system/timers.target.wants/anakia-build.timer → /etc/systemd/system/anakia-build.timer.

查看下次触发时间:

systemctl list-timers anakia-build.timer --no-pager

回显(实测):

NEXT                        LEFT     LAST PASSED UNIT               ACTIVATES
Wed 2026-10-14 02:30:00 UTC 10h left n/a  n/a    anakia-build.timer anakia-build.service

1 timers listed.
Pass --all to see loaded but inactive timers, too.

不等到夜里,手动触发一次验证全链路(service 由 timer 拉起后执行脚本、脚本执行 ant):

systemctl start anakia-build.service && systemctl status anakia-build.service --no-pager | head -6

回显(实测):

○ anakia-build.service - Anakia nightly site build
     Loaded: loaded (/etc/systemd/system/anakia-build.service; static)
     Active: inactive (dead) since Tue 2026-10-13 15:38:06 UTC; 10ms ago
TriggeredBy: ● anakia-build.timer
    Process: 15240 ExecStart=/opt/anakia-quick/build-site.sh (code=exited, status=0/SUCCESS)
   Main PID: 15240 (code=exited, status=0/SUCCESS)

(oneshot 服务执行成功后处于 inactive (dead) 是正常状态,成败看 status=0/SUCCESS。)构建日志进入 journal:

journalctl -u anakia-build.service --no-pager | grep -E "anakia|BUILD" | head -6

回显(实测):

Oct 13 15:38:05 anakia-u22 systemd[1]: Starting Anakia nightly site build...
Oct 13 15:38:06 anakia-u22 build-site.sh[15241]:    [anakia] Transforming into: /opt/anakia-quick/docs
Oct 13 15:38:06 anakia-u22 build-site.sh[15241]:    [anakia] Input:  guide.xml
Oct 13 15:38:06 anakia-u22 build-site.sh[15241]:    [anakia] Output: /opt/anakia-quick/docs/guide.html
Oct 13 15:38:06 anakia-u22 build-site.sh[15241]:    [anakia] Input:  index.xml
Oct 13 15:38:06 anakia-u22 build-site.sh[15241]:    [anakia] Output: /opt/anakia-quick/docs/index.html

定时构建的产物时间戳在每夜刷新,若前端有静态文件缓存,可在脚本里追加版本号改名或加 ETag 策略——超出本篇范围,此处给出的是「源变更 → 定时重建 → 产物目录」的最小闭环。timer 单元确认处于 active (waiting):

systemctl status anakia-build.timer --no-pager | head -4

回显(实测):

● anakia-build.timer - Run Anakia site build nightly
     Loaded: loaded (/etc/systemd/system/anakia-build.timer; enabled; vendor preset: enabled)
     Active: active (waiting) since Tue 2026-10-13 15:38:00 UTC; 6s ago
    Trigger: Wed 2026-10-14 02:30:00 UTC; 10h left

九、常见问题(FAQ)

Q1:源 XML 里直接写 & 或 < 会怎样?

源文件解析失败。实测把 <p>A&B 与转义演示</p> 这样带裸 & 的段落原样写进 index.xml 第九行后构建(照实回显,注意最后仍是 BUILD SUCCESSFUL):

Buildfile: /opt/anakia-quick/build.xml

docs:
    [mkdir] Created dir: /opt/anakia-quick/docs
   [anakia] Transforming into: /opt/anakia-quick/docs
   [anakia] Input:  guide.xml
   [anakia] Output: /opt/anakia-quick/docs/guide.html
   [anakia] Input:  index.xml
   [anakia]
   [anakia] Error: The reference to entity "B" must end with the ';' delimiter.
   [anakia]        Line: 9 Column: 11
   [anakia]

BUILD SUCCESSFUL
Total time: 0 seconds

两个教训:① & 必须写成 &amp;、< 必须写成 &lt;(或放进 CDATA 段);② AnakiaTask 对单个文件的解析/渲染失败只记 Error: 后继续处理其余文件,构建整体仍成功、该文件没有 HTML 产出——CI 里建议对日志做 grep -c "\[anakia\] Error:" 断言或比对 Input/Output 行数(该行为与排查手段见《配置参数详解》FAQ Q1)。

Q2:改了模板,产物没变化?

先确认改的是 templatePath 指向目录里的模板文件(本工程 styles/site.vsl),且没有把文件写进别处副本;再跑 ant rebuild 强制全量。增量机制下模板时间戳参与判定(正常 ant 就会重转引用它的文件,实测见《配置参数详解》5.3 节),只有系统时钟异常时才需要 rebuild。

Q3:生成的 HTML 中文乱码/变问号?

缺 output.encoding=UTF-8。Anakia 写文件用 Velocity 的 output.encoding,缺省回退 ISO-8859-1,JVM 层面改 file.encoding 无效。把第五节的 velocity.properties 配好并通过 velocityPropertiesFile 指向它即可,实测对比过程见《配置参数详解》5.4 节。

Q4:想加一个新页面需要动哪些文件?

三步:src/xml/ 下新增 <页面名>.xml(沿用 document/section/p 结构);src/xml/navigation.xml 菜单里加一个 <item>;跑 ant。模板与 build.xml 都不用动——这正是「结构与外观分离」后日常运维的成本。

Q5:Anakia 适合替代 Hugo/Jekyll 这类静态站生成器吗?

定位不同。Anakia 胜在与 Java 技术栈零依赖集成(一个 ant 命令、无运行时进程)、模板即 Velocity(Java 团队熟悉)、行为完全确定(每次全新 JVM);但它没有 Markdown 支持、没有主题生态、没有热重载。文档型站点与 Java 项目文档本地化(如源码 XML 化后的站点化)适合 Anakia;内容创作型站点选主流静态站生成器更省力。

十、参考链接