Apache Xalan-C++ 1.12 配置参数详解

Apache Xalan-C++ 1.12 配置参数全解:CMake 构建选项逐项对照实测(thread/transcoder/message-loader/system-encoding)、pkg-config 瑕疵、setStylesheetParam 三类重载与参数粘滞性、ErrorHandler 与 TraceListener 挂接、生命周期段错误根因与规避,全部结论来自 Ubuntu 22.04 真实构建与运行。

Apache XalanApache XalanC/C++XMLXSLT2026/10/16

Apache Xalan-C++ 1.12 配置参数详解

目标:把 Xalan-C++ 1.12 的"配置"一次讲透——构建期的 CMake 选项(逐项默认值、可选值、对照实验)、安装期的 pkg-config 与头文件布局、运行期的 XalanTransformer 配置 API(参数注入、错误处理、跟踪监听),以及最要命的生命周期作用域问题。全部内容基于 Ubuntu 22.04 + GCC 11 真实构建与运行实测。

一、环境信息

项目值
操作系统Ubuntu 22.04.5 LTS(x86_64)
编译器gcc/g++ 11.4.0,cmake 3.22.1
依赖Xerces-C++ 3.2.3(apt)、ICU 70.1
Xalan-C++1.12.0(库主版本 112)
构建目录/root/xalan-c-1.12-build(主线)、/root/xalan-c-1.12-build-alt(对照)
实验目录/root/xalanc-demo(CLI 与 API 演示文件)
管理账号root(密码 q1w2e3r4)

二、构建期配置:CMake 选项逐项实测

2.1 选项总表

1.12 的 CMake 缓存选项(选项名、实测可选值、主线构建默认值):

CMake 选项实测可选值默认值说明
threadstandard / posix / windowsstandard线程实现;std::thread 探测成功时 standard 排第一
transcodericu / defaulticu(检测到 ICU 时)字符转码后端
message-loaderinmemory / nls-broken / icu-brokeninmemory消息加载器;后两个上游源码标注 broken
message-localeen_USen_US消息 locale,实测源码 NLS 目录仅此一个
system-encodingascii / ebcdicascii系统字符编码(ebcdic 仅为 z/OS 准备)
BUILD_SHARED_LIBSON / OFFON共享库开关
CMAKE_INSTALL_PREFIX任意路径/usr/local安装前缀
BUILD_DOXYGENON / OFFOFFAPI 文档生成(检测到 Doxygen 才有意义)

选项名写法是带连字符的缓存变量,例如:cmake -Dthread=posix -Dtranscoder=default ../xalan_c-1.12。

2.2 thread:standard 与 posix 对照

主线构建(不传参)实测 summary:

--   Build shared libraries:      ON
--   Thread implementation:       standard

CMake 配置阶段会实际编译一段探测程序验证 std::thread 可用性,实测输出:

-- Performing Test XALAN_HAVE_STD_THREAD
-- Performing Test XALAN_HAVE_STD_THREAD - Success

对照实验:第二构建目录显式指定 -Dthread=posix -Dtranscoder=default,配置 summary 变为:

--   Installation directory:      /opt/xalan-alt
--   Build shared libraries:      ON
--   Thread implementation:       posix
--   Transcoder:                  default
--   Message Loader:              inmemory
--   Message Loader Locale:       en_US

💡 技术拓展:源码 cmake/XalanThreadSelection.cmake 的选择逻辑是——std::thread 探测通过才把 standard 加入候选,然后按 Threads::Threads 目标是否可用追加 posix(非 Windows)或 windows(MSVC)。standard 与 posix 在 ABI 上没有对外差异,只是内部互斥原语不同;常规 Linux 服务器保持默认 standard 即可。

2.3 transcoder:icu 与 default

源码 cmake/XalanTranscoderSelection.cmake 实测摘录:

if(ICU_FOUND)
  list(APPEND transcoders icu)
endif()
list(APPEND transcoders default)

实测环境有 ICU 70.1,故默认 icu。default 后端走操作系统本地转码(iconv 语义),字符集覆盖窄于 ICU。若构建机无 ICU,CMake 自动回落 default,不会失败。

2.4 message-loader:默认能用,另两个是 broken

源码 cmake/XalanMsgLoaderSelection.cmake 实测摘录(含上游注释原文):

check_function_exists(catgets HAVE_CATGETS)
if(HAVE_NLTYPES_H AND HAVE_CATOPEN AND HAVE_CATCLOSE AND HAVE_CATGETS)
  set(nls_available 1)
endif()
if(nls_available)
  list(APPEND msgloaders nls-broken)
endif()

# Note ICU support is broken.
if(ICU_FOUND)
  list(APPEND msgloaders icu-broken)
endif()

上游在选项值名字里直接标注了 broken(nls-broken、icu-broken),这不是命名戏称,是维护者对这两个后端状态的官方声明。生产构建固定用默认 inmemory。

2.5 message-locale:只有 en_US

locale 候选列表来自源码 NLS 目录下的 .xlf 消息文件(源码逻辑 file(GLOB locale_files ... src/xalanc/NLS/*/*.xlf)),1.12 源码树实际只有 en_US 一份,传其他值会在配置期报 Invalid message loader locale 类错误。想本地化错误消息,只能自己补 .xlf 文件。

2.6 system-encoding 与 BUILD_SHARED_LIBS

  • system-encoding=ascii 是 Linux 默认;ebcdic 分支仅当 CMAKE_SYSTEM_NAME STREQUAL "OS390"(IBM z/OS 大机)时成为默认,普通服务器不用碰。
  • BUILD_SHARED_LIBS 默认 ON,产物为 libxalan-c.so.112.0 + libxalanMsg.so.112.0。改 OFF 得静态库,链接业务程序时把 -lxalan-c -lxalanMsg 换成 .a 即可,其余 API 不变。

2.7 make 目标清单

构建目录 make help 实测目标(节选):

... all (the default if no target is provided)
... clean
... install
... test
... inmemory-dependencies
... locale
... samples
... xalan-c-headers
... CompileStylesheet ... Conf ... MsgCreator ... Performance
... SerializeNodeSet ... SimpleTransform ... SimpleXPathAPI ... SimpleXPathCAPI
... StreamTransform ... TestXPath ... TestXSLT ...

make test 等价于 ctest(21 项全过,见《Apache Xalan-C++ 在 Ubuntu 22.04 集成使用指南》)。注意没有 uninstall 目标,卸载见集成指南第八节。

三、安装期:pkg-config 与头文件

3.1 xalan-c.pc 的内容与空 Version 瑕疵

安装到 /usr/local/lib/pkgconfig/xalan-c.pc,实测全文:

prefix=/usr/local
exec_prefix=/usr/local
libdir=/usr/local/lib
includedir=/usr/local/include

Name: Xalan-C
Description: XSLT library for C/C++
Version: 
Libs: -L/usr/local/lib -lxalan-c
Cflags: -I/usr/local/include

两个实测发现:

  1. Version 字段为空:pkg-config --modversion xalan-c 返回空串。构建脚本要拿版本号请读 /usr/local/include/xalanc/Include/XalanVersion.hpp 的宏(XALAN_VERSION_MAJOR=1、MINOR=12、REVISION=0)。
  2. Libs 只带 -lxalan-c:libxalanMsg 与 libxerces-c 不在 .pc 的 Libs 行里,用 pkg-config 出来的链接行会缺库,需手工补 -lxalanMsg -lxerces-c。

3.2 头文件布局

ls /usr/local/include/xalanc/ 实测 15 个子目录:

DOMSupport        Harness           ICUBridge         Include
PlatformSupport   XMLSupport        XPath             XPathCAPI
XSLT              XalanDOM          XalanEXSLT        XalanExtensions
XalanSourceTree   XalanTransformer  XercesParserLiaison

日常开发最常打交道的三个:XalanTransformer(XalanTransformer.hpp 主入口)、XSLT(XSLTInputSource/XSLTResultTarget/TraceListenerDefault)、Include(PlatformDefinitions.hpp 必须第一个包含)。

四、运行期配置:XalanTransformer API 逐项实测

4.1 setStylesheetParam:三种参数类型三种重载

XalanTransformer 提供三类顶层参数注入重载,实测代码路径(/root/xalanc-demo/api/param_demo.cpp,编译 exit=0):

// 1) 字符串参数:值是 XPath 表达式,字符串字面量必须自带引号
theTransformer.setStylesheetParam(
    XalanDOMString("title"),
    XalanDOMString("'Q1 Score Review'"));

// 2) 数值参数:double 重载,直接传数
theTransformer.setStylesheetParam(XalanDOMString("threshold"), 60.0);

// 3) 节点集参数:先 parseSource 再传文档指针
const XalanParsedSource* parsedExtra = 0;
if (theTransformer.parseSource(
        XSLTInputSource("/root/xalanc-demo/api/extra.xml"),
        parsedExtra) == 0)
{
    theTransformer.setStylesheetParam(
        XalanDOMString("extra"),
        parsedExtra->getDocument());
}

配套样式表 param_demo.xsl 声明三个参数并在模板中引用($title、$threshold、$extra),输出 <report title="{$title}" threshold="{$threshold}"> 并统计 <passed count="{count(student[score &gt;= $threshold])}"/>。

4.2 字符串参数的 XPath 语义(CLI 与 API 双路径实测)

这是 1.12 最容易踩的语义坑:setStylesheetParam 的字符串版本与 CLI 的 -p 接受的都是 XPath 表达式,不是字面字符串。传裸文本实测结果(证据:CLI 场景 + API 场景双路径,同一结论):

CLI 路径,裸字符串(样式表引用了 $title):

$ Xalan -p title "Q1 Score" -o /dev/null hello.xml api/param_demo.xsl
XPath error: Extra illegal token.
expression = 'Q1 Score' Remaining tokens are:  ( 'Score') (Occurred in an unknown entity, at an unknown location.)
XPathParserException: Extra illegal token.
expression = 'Q1 Score' Remaining tokens are:  ( 'Score') (Occurred in an unknown entity, at an unknown location.)
exit=255

API 路径,裸字符串:

XPath error: Extra illegal token.
expression = 'Q1 Score' Remaining tokens are:  ( 'Score') (Occurred in an unknown entity, at an unknown location.)
bare result=-1 lastError=XPathParserException: Extra illegal token.
expression = 'Q1 Score' Remaining tokens are:  ( 'Score') (Occurred in an unknown entity, at an unknown location.)

加上 XPath 字符串引号后(CLI 用 printf "\x27Q1 Score\x27" 构造实参 'Q1 Score'):

exit=0
<?xml version="1.0" encoding="UTF-8"?>
<report title="Q1 Score" threshold="80">
<passed count="2"/>
</report>

API 路径传 "'Q1 Score'" 同样 result=0,且输出 title 属性正确带上空格文本 Q1 Score。

两个附带实测发现:

  1. 顶层参数惰性求值:样式表不引用该参数时,裸字符串错误不会触发(实测样式表只含 $passLine 时传裸 "Q1 Score" 依旧 exit=0)。错误只在表达式被真正求值时暴露。
  2. 数值参数无需引号:-p passLine 70(CLI)与 double 重载直接生效,实测把 Charlie 的 78 分从 FAIL 翻成 PASS。

4.3 参数粘滞性与 clearStylesheetParams(三次运行对照)

官方样例源码注释原文(UseStylesheetParam 样例):

// Clear the top-level parameters - Note: They are sticky across transformations.
theXalanTransformer.clearStylesheetParams();

"sticky(粘滞)"实测验证:同一个 XalanTransformer 实例连续三次变换,只改 threshold 参数:

运行参数状态实测输出
run1title='Q1 Score Review'、threshold=60、extra 节点集<report title="Q1 Score Review" threshold="60"> <passed count="3"/> <extra-items count="0"/>
run2仅重设 threshold=90,title/extra 不动<report title="Q1 Score Review" threshold="90"> <passed count="1"/> <extra-items count="0"/>
run3clearStylesheetParams() 后<report title="" threshold="80"> <passed count="2"/>(title 清空、threshold 回样式表默认 80、extra 节点消失)

run1→run2 证明参数在实例生命周期内跨变换保留;run3 证明 clearStylesheetParams 一次性清空全部注入值。程序结束前记得 destroyParsedSource 释放节点集参数的解析产物。

💡 技术拓展:Web 服务类长驻进程若复用同一 Transformer 处理多用户请求,粘滞性等于用户 A 的参数会泄漏进用户 B 的请求——要么每次请求前 clear,要么每请求一个实例。这是 Xalan-C++ 与多数现代 XSLT 库 API 习惯差异最大的一点。

4.4 错误处理:默认路径与 setErrorHandler

不装 ErrorHandler 时,解析错误走默认路径:transform 返回负值,错误文本进 getLastError。实测(broken.xml 内容为 <a><b></a>):

== 场景一:默认错误路径 ==
result=-2 getLastError=SAXParseException: expected end of tag 'b' (Occurred in entity '/root/xalanc-demo/api/broken.xml', at line 1, column 9.)

装自定义 ErrorHandler(继承 xercesc::ErrorHandler 并实现 warning/error/fatalError/resetErrors)后:

== 场景二:自定义 ErrorHandler ==
[MyErrorHandler fatalError] line 1, col 9: expected end of tag 'b'
result=0 getLastError=

注意返回值语义反转:安装了 ErrorHandler 后 transform 返回 0——因为错误已"转交"应用层处理,Xalan 认为 fatal error 被应用知晓,不再计入自身错误码。应用层要在自己的 fatalError 里决定中止逻辑,不能再看返回值。

ErrorHandler 的挂取接口(头文件实测摘录):

void setErrorHandler(ErrorHandler* theErrorHandler);
ErrorHandler* getErrorHandler() const;

4.5 消息流配置:setWarningStream / setErrorStream / setProblemListener

头文件实测摘录(XalanTransformer.hpp):

// Set the ostream instance for reporting errors. The default is a null
// pointer, so errors are not reported.
void setErrorStream(StreamType* theStream);

// Set the ostream instance for reporting warnings and messages. The default
// is std::cerr. If set to a null pointer, no warnings or messages are written.
void setWarningStream(StreamType* theStream);

要点:警告流默认 std::cerr,错误流默认空(不输出)。两者都会被 setProblemListener 抢占——装了 ProblemListener 后所有问题回调走它,流配置失效。相关方法还有 setIndent(int)(覆盖样式表缩进设置)与 setOmitMETATag(控制 HTML 输出的 META 标签生成,对应 CLI 的 -m)。

4.6 addTraceListener:把 CLI 的四种跟踪搬到 API

TraceListenerDefault 构造函数四个布尔参数与 CLI 选项一一对应(TraceListen 官方样例源码实测摘录):

TraceListenerDefault theTraceListener(
        diagnosticsWriter,                     // PrintWriter,不是 ostream
        theXalanTransformer.getMemoryManager(),
        traceTemplates,         // -tt 模板调用
        traceGenerationEvent,   // -tg 生成事件
        traceSelectionEvent,    // -ts 选择事件
        traceTemplateChildren); // -ttc 模板子节点
theXalanTransformer.addTraceListener(&theTraceListener);

实测要点:

  1. 第一个参数是 PrintWriter 引用(如 XalanOutputStreamPrintWriter),直接传 ostream 指针编译不过(实测报 no matching function,见第六节)。
  2. 诊断输出通道建议 theStdErr.setThrowTranscodeException(false),避免跟踪内容转码失败抛异常。
  3. 实测 run5(traceTemplates=true)对成绩单变换打印 xsl:template match='/'、xsl:template match='student' 等逐模板调用记录;四种模式的完整输出样例见《Apache Xalan-C++ 1.12 常用 CLI 命令与工具详解》TraceListen 一节。

五、生命周期与线程安全

5.1 段错误根因实测:Transformer 析构晚于 terminate()

反例程序(ns_test.cpp 关键结构):XalanTransformer 声明在 main 顶层(无内层作用域),变换成功后调用 terminate 序列:

XalanTransformer t;          // 顶层作用域:析构被推迟到 main 末尾
const XalanParsedSource* ps = 0;
t.parseSource(...);          // 节点集参数
t.setStylesheetParam(...);
t.transform(...);            // result=0,输出正确
t.destroyParsedSource(ps);
...
XalanTransformer::terminate();   // 全局终止
XMLPlatformUtils::Terminate();

实测运行:输出完全正确(<report ...> 生成无误),但进程退出时段错误:

result=0
exit=139

连续重复 5 次全部复现(run1-run5 均 exit=139)。根因:全局 terminate() 释放了 Transformer 依赖的底层资源,而顶层作用域的 t 的析构函数要到 main 结束才执行,析构时踩到已释放资源。

规避写法(官方 16 个样例的统一做法):实例放进内层 { }:

XMLPlatformUtils::Initialize();
XalanTransformer::initialize();
{
    XalanTransformer t;      // 内层作用域
    ... 全部变换 ...
}                            // t 在这里析构,早于 terminate
XalanTransformer::terminate();
XMLPlatformUtils::Terminate();
XalanTransformer::ICUCleanUp();

修正后实测 exit=0,稳定通过。

5.2 生命周期五模式探测结论

为定位上述问题做了系统探测(ns_probe,同一份节点集参数代码在五种作用域/销毁组合下运行):

模式行为transform 结果进程退出
0parse 但不 set 不销毁,内层作用域-1(参数无效)0
1parse + setParam + destroyParsedSource00
2parse + setParam 不 destroy,内层作用域-10
3parse 不 set + destroy-10
4主源换成节点集文件本身 + setParam + destroy00

结论三条:节点集参数必须 set 进参数表才能让 transform 成功(模式 0/3);内层作用域下即使忘 destroy 也不会崩(模式 2,仅结果错误);崩溃只与作用域有关(5.1 的顶层声明场景)。

5.3 线程模型

XalanTransformer 实例非线程安全,全局 initialize/terminate 进程内各调一次。官方 ThreadSafe 样例的并发模式是"每线程独立 Transformer":实测三线程同时变换(各持实例)正常完成,输出 Clock before starting threads: 7006 及三个线程启动记录,exit=0。共享实例的并发访问无保护,不要尝试。

六、编译期 API 常见错误实测

首次编写 param_demo.cpp 时实测吃到三类编译错误(GCC 11 真实报错,修订后 exit=0),后人可直接对照规避:

错误一:Xerces 3.x 的 getMessage() 返回裸指针,没有 .transcode() 成员

error: request for member 'transcode' in '(& e)->xercesc_3_2::SAXParseException::<anonymous>.xercesc_3_2::SAXException::getMessage()', which is of non-class type 'const XMLCh*' {aka 'const char16_t*'}

正确写法是用独立工具函数转码:XMLString::transcode(e.getMessage(), msg, 255)。

错误二:parseSource 第二参数是"指针的 const 引用"

error: binding reference of type 'const xalanc_1_12::XalanParsedSource*&' to 'xalanc_1_12::XalanParsedSource*' discards qualifiers
note: initializing argument 2 of 'int xalanc_1_12::XalanTransformer::parseSource(const xalanc_1_12::XSLTInputSource&, const XalanParsedSource*&, bool)'

声明变量时必须带 const:const XalanParsedSource* parsedExtra = 0;。

错误三:TraceListenerDefault 构造第一参数是 PrintWriter 不是 ostream

error: no matching function for call to 'TraceListenerDefault::TraceListenerDefault(xercesc_3_2::MemoryManager&, std::ostream*, bool, bool, bool, bool)'
note: candidate: 'TraceListenerDefault(PrintWriter&, MemoryManager&, bool, bool, bool, bool)'

需要先用 XalanStdOutputStream theStdErr(cerr); XalanOutputStreamPrintWriter diagnosticsWriter(theStdErr); 包装两步再传入。

七、注意事项与常见问题

Q1:setStylesheetParam 传字符串怎么带空格? 值按 XPath 解析,字面量要自带引号:"'Q1 Score'"(外层 C++ 引号包住内层 XPath 单引号)。裸文本实测报 XPath error: Extra illegal token。

Q2:参数设置了但不生效? 查两件事:样式表是否真的引用了该参数(顶层参数惰性求值);是否忘了粘滞性——上次 run 设置的同名参数还在生效,需要 clearStylesheetParams。

Q3:transform 返回 -1/-2 怎么区分? -1 多为 XPath/参数类错误(看 getLastError),-2 多为源文档解析失败(SAXParseException 细节在 getLastError)。装了 ErrorHandler 后错误转交应用层,返回值可能变 0。

Q4:程序输出正确但退出时段错误? Transformer 声明作用域问题,见 5.1 节。所有实例放内层 { },保证析构先于 terminate()。

Q5:构建期能不能安全改 message-loader 换本地化消息? 不能,上游源码把 nls/icu 两个加载器标注为 broken(实测选项名 nls-broken/icu-broken)。本地化需求保持 inmemory,消息以 en_US 为准。

Q6:pkg-config --modversion xalan-c 返回空? 1.12 的已知瑕疵(Version 字段空)。版本检测改读 XalanVersion.hpp 宏。

八、参考链接

  1. Xalan-C++ 官方文档:https://xalan.apache.org/xalan-c/
  2. XalanTransformer 头文件(安装后):/usr/local/include/xalanc/XalanTransformer/XalanTransformer.hpp
  3. 官方样例源码:https://github.com/apache/xalan-c/tree/master/samples
  4. W3C XSLT 1.0 顶层参数语义:https://www.w3.org/TR/1999/REC-xslt-19991116#top-level-variables
  5. 本专栏相关文档:《Apache Xalan-C++ 在 Ubuntu 22.04 集成使用指南》《Apache Xalan-C++ 1.12 常用 CLI 命令与工具详解》《Apache Xalan-C++ 介绍与选型指南》