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=&lt;someTag/&gt;
PHP xsl_myF2=<someTag/>
PHP xsl_myF3=
    <aBigFragment> text <someTag val="123"/> text </aBigFragment>

XSL 接收外部 XML 作为 DOMElement

所有 XSLTProcessor 操作依赖于 DOMDocument 操作,因此为了更好的性能,最好传递一个 DOMElement 对象,而不是字符串。

<xsl:copy-of ... /> 可以接收 DOMElementDOMDocument,而 <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 片段时,这种方式非常有效。
Last modified: Friday, 10 January 2025, 2:19 AM