PHP编程
XSLTProcessor::registerPHPFunctions() 方法
XSLTProcessor::registerPHPFunctions() 方法允许将 PHP(v5.0.4 及更高版本)函数作为 XSLT v1 函数使用。它是“PHP 调用的 XSLT 解析器”的一个 XSLT/registerFunction 功能。
这是 XSLTProcessor 的一个功能,用于将 PHP 函数或方法暴露给 XSLT 脚本(由 importStyleSheet 方法处理)。对于 PHP 用户来说,这一点非常重要,因为 PHP(以及任何依赖 libxml2 的库)并没有 XSLT v2 引擎,而这种功能上的缺失部分可以通过使用 registerPHPFunctions 来克服。然而,即便在 2013 年,许多程序员仍然认为:
... 它文档支持差,且有许多不合理的地方。尽可能少依赖它 ...
– F. Avila 表示
本章的目标是提供一个教程,教你如何在 XSLT 中使用 PHP 函数,尝试改变这种“不太理想的局面”。
注意:另一个功能补充是使用 PHP 对 EXSLT 库的支持(请参见 http://www.exslt.org/)。请参阅 [1]、[2]、[3] 等资源及其他技巧(不要与类似名称的函数库混淆)。Common Module 是与 registerPHPFunctions 一起使用时最重要的模块,所有 XML 解析器都完整实现了它。
准备工作
在调用 XSLTProcessor 之前需要进行一些初始化,因此我们可以将这些初始化步骤封装在一个函数中,该函数将 XML 数据和 XSLT 脚本作为输入,并打印 XSLTProcessor 的结果。
function XSL_transf($xml, $xsl) {
$xmldoc = DOMDocument::loadXML($xml);
$xsldoc = DOMDocument::loadXML($xsl);
$proc = new XSLTProcessor();
$proc->registerPHPFunctions();
$proc->importStyleSheet($xsldoc);
echo $proc->transformToXML($xmldoc);
}
要将 XSLT 脚本传递给 XSL_transf() 函数,XSLT 脚本必须是一个字符串,因此我们可以使用内联声明(见 PHP 的 Nowdoc 和 Heredoc 语法):
$xsl = <<<'EOB'
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:php="http://php.net/xsl">
<xsl:template match="/">
...
</xsl:template>
</xsl:stylesheet>
EOB;
XSL_transf('<root/>', $xsl);
另一种方式是从文件中获取 XSLT 脚本:
XSL_transf('<root/>', file_get_contents('xslt_script.xsl'));
或者,修改 XSL_transf() 函数,使其支持文件输入:
function XSL_transf($xmlFile, $xslFile) {
$xmldoc = DOMDocument::load($xmlFile);
$xsldoc = DOMDocument::load($xslFile);
// 其余代码不变...
}
注意事项
在 <xsl:stylesheet version="1.0" ...> 声明后,你可以通过例如以下方式改变默认的输出格式:
<xsl:output method="text"/>
在本教程的示例中,我们始终使用 XML 方法:
<xsl:output method="xml" encoding="utf-8" indent="yes"/>
并且,为了查看所有标签而不需要在浏览器中打开源代码,可以通过以下代码在 PHP 脚本的开头设置 HTTP 响应头:
header("Content-Type: text/plain; charset=utf-8");
使用 PHP 函数与静态 XSLT
在“将 PHP 暴露给 XSLT”的初步概述中,我们可以忽略 XML 输入数据,将 XSLT 脚本作为静态模板来使用。
XSL 接收外部字符串值
声明并使用包含 PHP 函数调用(见 xsl:value-of)的 XSLT 脚本,从 PHP 函数(直接或带参数)导入字符串值。
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:php="http://php.net/xsl" exclude-result-prefixes="php">
<xsl:template match="/">
PHP time()=<xsl:value-of select="php:function('time')" />,
PHP rand()=<xsl:value-of select="php:function('rand')" />,
PHP rand(11,99)=<xsl:value-of select="php:function('rand',11,99)" />,
PHP xsl_myF1()=<xsl:value-of select="php:function('xsl_myF1_StrConstant')" />,
PHP xsl_myF2(XX)=<xsl:value-of select="php:function('xsl_myF2_id','XX')" />.
</xsl:template>
</xsl:stylesheet>
前两个函数可以不带任何参数调用,后两个函数是用户声明的:
function xsl_myF1_StrConstant() { return "123"; }
function xsl_myF2_id($str) { return $str; }
XSLT 结果:
PHP time()=1365869487,
PHP rand()=1410713536,
PHP rand(11,99)=20,
PHP xsl_myF1()=123,
PHP xsl_myF2(XX)=XX.
XSL 接收外部 XML 字符串
<xsl:value-of ... /> 通常接收字符串值,但通过 disable-output-escaping 属性,它可以接收整个 XML 片段。
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:php="http://php.net/xsl">
<xsl:output method="xml" encoding="utf-8" indent="yes"/>
<xsl:template match="/">
PHP xsl_myF2('<someTag/>')=<xsl:value-of select="php:function('xsl_myF2_id','<someTag/>')" />
PHP xsl_myF2('<someTag/>')=<xsl:value-of select="php:function('xsl_myF2_id','<someTag/>')" disable-output-escaping="yes"/>
PHP xsl_myF3()=<xsl:value-of select="php:function('xsl_myF1_XmlConstant')" disable-output-escaping="yes"/>
</xsl:template>
</xsl:stylesheet>
这样可以在 XSLT 中处理 XML 内容并调用 PHP 函数。
这里
function xsl_myF1_XmlConstant() {
return '<aBigFragment> text <someTag val="123"/> text </aBigFragment>';
}
XSL_transf 结果:
PHP xsl_myF2=<someTag/>
PHP xsl_myF2=<someTag/>
PHP xsl_myF3=
<aBigFragment> text <someTag val="123"/> text </aBigFragment>
XSL 接收外部 XML 作为 DOMElement
所有 XSLTProcessor 操作依赖于 DOMDocument 操作,因此为了更好的性能,最好传递一个 DOMElement 对象,而不是字符串。
<xsl:copy-of ... /> 可以接收 DOMElement 或 DOMDocument,而 <xsl:for-each ...> 接收 DOMNodeList。因此,如果我们有一个返回 DOMDocument 的 PHP 函数,可以直接使用它。
function xsl_myF4_DOMConstant() {
static $xdom = DOMDocument::loadXML('<t> foo <tt val="123"/> bar </t>');
return $xdom;
}
在 XSLT 脚本中调用 xsl_myF4:
<xsl:template match="/">
PHP xsl_myF4()=<xsl:copy-of select="php:function('xsl_myF4_DOMConstant')" />
</xsl:template>
结果:
PHP xsl_myF4()=<t> foo <tt val="123"/> bar </t>
XSL 接收外部片段
常见需求是处理 DOM 片段,即没有根节点的 XML。例如,在函数 xsl_myF4_DOMConstant() 中,我们使用了 <t> foo <tt val="123"/> bar </t>。如果返回值只是 foo <tt val="123"/> bar,函数需要修改为:
function xsl_myF4b_DOMFrag() {
$dom = new DOMDocument;
$tmp = $dom->createDocumentFragment();
$tmp->appendXML(' <t> foo <tt val="123"/> bar </t> TEST');
return $tmp;
}
现在,调用 xsl_myF4b 与调用 xsl_myF4 不同,我们需要更改 XPath 表达式以引用一组节点:
<xsl:template match="/">
PHP xsl_myF4b()=<xsl:copy-of select="php:function('xsl_myF4b_DOMFrag')/node()" />
</xsl:template>
注意:这可能是 LibXML2 的一个 bug,详细解释请参考相关文档。
结果:
PHP xsl_myF4b()= <t> foo <tt val="123"/> bar </t> TEST
使用动态 XSLT 的 PHP 函数
“现实生活”中的模板使用 XML 输入数据来生成输出。假设以下 XML 数据:
<allusers>
<user> <uid>bob</uid> </user>
<user> <uid>joe</uid> </user>
</allusers>
XSL 发送和接收字符串值
要将输入节点作为字符串发送,可以使用 XPath-v1.0 中的 string() 函数,它会将节点转换为字符串。如果参数是 XML 片段(即包含多个值的节点),则“类型转换为字符串”会将标签替换为空格。
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:php="http://php.net/xsl">
<xsl:output method="xml" encoding="utf-8" indent="yes"/>
<xsl:template match="allusers">
Users:
<xsl:for-each select="user">
<xsl:value-of select="position()"/>:
myF2(uid)="<xsl:value-of select="php:function('xsl_myF2_id',string(uid))" />",
myF2(.)="<xsl:value-of select="php:function('xsl_myF2_id',string(.))" />",
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
XSL_transf 结果:
Users:
1:
myF2(uid)="BOB",
myF2(.)=" BOB textTest ",
2:
myF2(uid)="JOE",
myF2(.)=" JOE ",
通过 DOM 进行 XSL 注册函数通信
最完整的方式是,XSLT 脚本将节点作为 PHP 函数参数发送,且不进行字符串类型转换。PHP 函数将接收一个 DOMElement 数组作为参数,PHP 可以将一个 DOMElement 发送回 XSLT 脚本。
这是使用这种“DOM 通信”实现的身份函数:
function xsl_myF5_id($m) { // $m 总是一个数组
$ele = $m[0]; // get_class($m[0])==DOMElement
return $ele; // XSLT 只接受 DOMElement 或 DOMDocument
}
在输入节点上使用此函数的循环:
<xsl:for-each select="user">
<xsl:value-of select="position()"/>:
copy-of myF5(uid)="<copy-of select="php:function('xsl_myF5_id', uid)" />",
value-of myF5(uid)="<xsl:value-of select="php:function('xsl_myF5_id', uid)" />",
copy-of myF5(.)=<xsl:copy-of select="php:function('xsl_myF5_id', . )" />.
</xsl:for-each>
XSL_transf 结果:
1:
copy-of myF5(uid)="<uid>BOB</uid>",
value-of myF5(uid)="BOB",
copy-of myF5(.)=<user> <uid>BOB</uid> textTest </user>.
2:
copy-of myF5(uid)="<uid>JOE</uid>",
value-of myF5(uid)="JOE",
copy-of myF5(.)=<user> <uid>JOE</uid> </user>.
使用数组:
像 xsl_myF5_id 这样的函数可以返回 NULL,从而不会产生任何干扰。对于数组(或数据库)的组成,这在稍后通过另一个函数返回给 XSLT 时非常有用。
XSLT 全局参数
有多种方式可以将 PHP 变量作为全局参数传递给 XSLT:
- 调用返回 PHP 变量值的
php:function; - 使用解析器中的
setparameter来从xsl:parameter声明创建真实的 XSLT 变量; - 将“参数 XML”注入到 XML 输入中。
第一种方法可能是最好的,但每种方式都有其优缺点。
特定参数的用户函数
与 setParameter(如下节所示)相比,函数的优势在于能够携带 XML 片段(不仅仅是字符串值),但它不被 XSLT 当作普通变量访问。典型的用法如下:
<xsl:value-of select="php:function('xsl_strParam','param1')" />
在 PHP 中的实现如下:
function xsl_strParam($paramName) {
global $PARAMS;
return $PARAMS[$paramName];
}
设置 XSLT 全局参数
全局参数在样式表级别进行定义:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:param name="param1" select="'default-string1'"/>
</xsl:stylesheet>
它们可以具有默认值,通过 select 语句指定。全局参数可用于将外部应用程序的值传递到样式表中。
提示:你可以在 select 属性中使用 XPath,例如 <xsl:param name="p1" select="." />,当它不是 XPath 时,记得使用 "'string'"。
使用 XSLTProcessor::setParameter
修改 XSL_transf()(在准备部分中)以支持参数:
function XSL_transf($xml, $xsl, $param1val) {
$xmldoc = DOMDocument::loadXML($xml);
$xsldoc = DOMDocument::loadXML($xsl);
$proc = new XSLTProcessor();
$proc->registerPHPFunctions();
$proc->importStyleSheet($xsldoc);
$proc->setParameter('', 'param1', $param1val); // 这里添加,$param1val 将覆盖 'default-string1'
echo $
proc->transformToXML($xmldoc); }
##### **XML 注入作为参数**
另一种自然的方式是将参数作为 XML 输入字符串的一部分来读取。当有许多参数或 XML 片段时,这种方式非常有效。