Skip to content

XSL 转换

XSLT(Extensible Stylesheet Language Transformations)是一种将 XML 文档转换为其他格式(XML、HTML、文本)的语言。PHP 的 XSL 扩展基于 libxslt 库,提供了完整的 XSLT 1.0 处理支持。本节将讲解如何使用 PHP 进行 XSL 转换。

前置知识

阅读本节前,建议先了解:DOM 操作XMLReader / XMLWriter

基础概念

XSLT 的用途

  • XML 转 HTML:将数据 XML 转换为网页
  • XML 到 XML:文档格式转换(如 RSS 转 Atom)
  • 数据提取:从复杂 XML 中提取特定数据
  • 报告生成:将 XML 数据转换为可读的报告

安装

bash
# 编译安装
./configure --with-xsl

# Ubuntu/Debian
sudo apt-get install php-xsl

XSLT 转换基础

基本转换流程

php
<?php
declare(strict_types=1);

// XML 数据
$xmlString = <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<books>
    <book category="fiction">
        <title>Harry Potter</title>
        <author>J.K. Rowling</author>
        <year>2005</year>
        <price>29.99</price>
    </book>
    <book category="programming">
        <title>Clean Code</title>
        <author>Robert C. Martin</author>
        <year>2008</year>
        <price>39.99</price>
    </book>
</books>
XML;

// XSL 样式表
$xslString = <<<XSL
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="html" encoding="UTF-8" indent="yes"/>
    <xsl:template match="/">
        <html>
        <head><title>书籍列表</title></head>
        <body>
            <h1>书籍列表</h1>
            <table border="1">
                <tr>
                    <th>书名</th>
                    <th>作者</th>
                    <th>年份</th>
                    <th>价格</th>
                </tr>
                <xsl:for-each select="books/book">
                    <tr>
                        <td><xsl:value-of select="title"/></td>
                        <td><xsl:value-of select="author"/></td>
                        <td><xsl:value-of select="year"/></td>
                        <td>$<xsl:value-of select="price"/></td>
                    </tr>
                </xsl:for-each>
            </table>
        </body>
        </html>
    </xsl:template>
</xsl:stylesheet>
XSL;

// 加载 XML
$xml = new DOMDocument();
$xml->loadXML($xmlString);

// 加载 XSL
$xsl = new DOMDocument();
$xsl->loadXML($xslString);

// 创建 XSLT 处理器
$processor = new XSLTProcessor();
$processor->importStyleSheet($xsl);

// 执行转换
$result = $processor->transformToXML($xml);
echo $result;

转换为 DOMDocument

php
<?php
declare(strict_types=1);

$processor = new XSLTProcessor();
$processor->importStylesheet($xslDoc);

// 转换为 DOMDocument(可进一步操作)
$domResult = $processor->transformToDoc($xmlDoc);

// 转换为文件
$success = $processor->transformToURI($xmlDoc, '/tmp/output.html');

// 转换为字符串
$html = $processor->transformToXML($xmlDoc);

传递参数

设置 XSLT 参数

php
<?php
declare(strict_types=1);

$xslString = <<<XSL
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="html" encoding="UTF-8"/>
    <xsl:param name="title" select="'书籍列表'"/>
    <xsl:param name="currency" select="'USD'"/>
    <xsl:param name="minPrice" select="0"/>

    <xsl:template match="/">
        <html>
        <body>
            <h1><xsl:value-of select="$title"/></h1>
            <xsl:for-each select="books/book[price &gt;= $minPrice]">
                <p>
                    <xsl:value-of select="title"/>
                    - <xsl:value-of select="$currency"/> <xsl:value-of select="price"/>
                </p>
            </xsl:for-each>
        </body>
        </html>
    </xsl:template>
</xsl:stylesheet>
XSL;

$processor = new XSLTProcessor();
$processor->importStylesheet($xslDoc);

// 传递参数
$processor->setParameter(null, 'title', '我的书架');
$processor->setParameter(null, 'currency', 'CNY');
$processor->setParameter(null, 'minPrice', 30);

// 也可以指定命名空间
// $processor->setParameter('http://example.com/ns', 'param', 'value');

$html = $processor->transformToXML($xmlDoc);
echo $html;

注册 PHP 函数

在 XSLT 中调用 PHP 函数

php
<?php
declare(strict_types=1);

// PHP 函数:将价格格式化为人民币
function formatPrice(string $price): string
{
    return '¥' . number_format((float) $price, 2);
}

// PHP 函数:计算折扣价
function applyDiscount(string $price, string $discount): string
{
    return number_format((float) $price * (float) $discount, 2);
}

$processor = new XSLTProcessor();
$processor->importStylesheet($xslDoc);

// 注册 PHP 函数供 XSLT 调用
$processor->registerPHPFunctions(['formatPrice', 'applyDiscount']);

$xslWithPhp = <<<XSL
<?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:output method="html" encoding="UTF-8"/>
    <xsl:template match="/">
        <html><body>
            <xsl:for-each select="books/book">
                <p>
                    <xsl:value-of select="title"/>
                    - <xsl:value-of select="php:function('formatPrice', price)"/>
                </p>
                <p>
                    折扣价: <xsl:value-of select="php:function('applyDiscount', price, '0.8')"/>
                </p>
            </xsl:for-each>
        </body></html>
    </xsl:template>
</xsl:stylesheet>
XSL;

安全警告

registerPHPFunctions 允许 XSLT 调用任意 PHP 函数,存在安全风险。仅在可信的 XSL 样式表中使用,或使用白名单限制可调用的函数。

详细说明

XSLTProcessor 方法一览

方法说明
importStyleSheet()导入 XSL 样式表
transformToDoc()转换为 DOMDocument
transformToURI()转换并写入文件/URI
transformToXML()转换为 XML 字符串
setParameter()设置 XSLT 参数
getParameter()获取 XSLT 参数
removeParameter()移除参数
registerPHPFunctions()注册 PHP 函数
hasExsltSupport()检查 EXSLT 扩展支持
setProfiling()启用性能分析
getSecurityPrefs()获取安全设置
setSecurityPrefs()设置安全选项

安全配置

php
<?php
declare(strict_types=1);

$processor = new XSLTProcessor();

// 安全选项
// XSL_SECPREF_NONE         - 无限制
// XSL_SECPREF_READ_FILE   - 允许读取文件
// XSL_SECPREF_WRITE_FILE  - 允许写入文件
// XSL_SECPREF_CREATE_DIRECTORY - 允许创建目录
// XSL_SECPREF_READ_NETWORK   - 允许网络访问
// XSL_SECPREF_WRITE_NETWORK  - 允许网络写入

// 禁止所有文件和网络操作(最安全)
$processor->setSecurityPrefs(XSL_SECPREF_NONE);

// 只允许读取文件
$processor->setSecurityPrefs(XSL_SECPREF_READ_FILE);

EXSLT 扩展

php
<?php
declare(strict_types=1);

$processor = new XSLTProcessor();

// 检查 EXSLT 支持
if ($processor->hasExsltSupport()) {
    echo "支持 EXSLT" . PHP_EOL;

    // EXSLT 命名空间
    // xmlns:math="http://exslt.org/math"
    // xmlns:set="http://exslt.org/sets"
    // xmlns:str="http://exslt.org/strings"
    // xmlns:date="http://exslt.org/dates-and-times"
    // xmlns:dyn="http://exslt.org/dynamic"

    // 示例 XSL 使用 EXSLT 数学函数
    // <xsl:value-of select="math:max(price)"/>
    // <xsl:value-of select="math:min(price)"/>
    // <xsl:value-of select="str:uppercase(title)"/>
}

实战示例

XML 数据报表生成

php
<?php
declare(strict_types=1);

class ReportGenerator
{
    private XSLTProcessor $processor;

    public function __construct()
    {
        $this->processor = new XSLTProcessor();
    }

    /**
     * 从 XML 数据和 XSL 模板生成 HTML 报表
     */
    public function generate(
        string $xmlContent,
        string $xslContent,
        array $params = []
    ): string {
        $xml = new DOMDocument();
        $xml->loadXML($xmlContent);

        $xsl = new DOMDocument();
        $xsl->loadXML($xslContent);

        $this->processor->importStylesheet($xsl);

        // 设置参数
        foreach ($params as $name => $value) {
            $this->processor->setParameter(null, $name, $value);
        }

        return $this->processor->transformToXML($xml);
    }

    /**
     * 生成并保存到文件
     */
    public function generateToFile(
        string $xmlPath,
        string $xslPath,
        string $outputPath,
        array $params = []
    ): void {
        $xml = new DOMDocument();
        $xml->load($xmlPath);

        $xsl = new DOMDocument();
        $xsl->load($xslPath);

        $this->processor->importStylesheet($xsl);

        foreach ($params as $name => $value) {
            $this->processor->setParameter(null, $name, $value);
        }

        $this->processor->transformToURI($xml, "file://{$outputPath}");
    }
}

注意事项

常见错误

php
<?php
declare(strict_types=1);

// XSL 编译错误 - 检查 XSL 语法
try {
    $processor->importStylesheet($xslDoc);
} catch (\Throwable $e) {
    echo "XSL 错误: " . $e->getMessage();
}

// 转换错误
libxml_use_internal_errors(true);
$result = $processor->transformToXML($xmlDoc);
foreach (libxml_get_errors() as $error) {
    echo "错误: {$error->message}" . PHP_EOL;
}
libxml_clear_errors();

最佳实践

  1. 启用 profiling:开发时使用 setProfiling() 分析转换性能
  2. 使用参数:通过 setParameter 动态控制输出
  3. 安全配置:生产环境设置 setSecurityPrefs
  4. 缓存样式表:避免重复加载 XSL 文件
  5. 使用 EXSLT:利用扩展函数增强 XSLT 能力

下一节

继续学习:libxml

参考链接