PHP导出含图片Word文档的MHT原理与实现
在使用PHP生成Word报表或文档时,开发者常面临如何将HTML样式及其中包含的图片完整导出为 .doc 文件的难题。传统的COM扩展方式效率低且受限于Windows环境,而直接修改后缀名的方法往往导致图片丢失。本文将基于MHTML(Web归档格式)的特性,详细解析如何在PHP中构建一个能自动提取、内嵌图片的Word生成方案。
技术方案对比
在PHP中生成Word文档主要有两种思路,但它们在处理图片和兼容性上表现差异显著:
- COM扩展调用 (php_com): 利用服务器上的Office软件引擎(调用
word.application)。这种方式虽然能生成最标准的DOCX/DOC,但需要服务器安装Office,执行时需启动客户端进程,资源占用极高,且在Linux环境下不可用,故不推荐用于高并发场景。 - MHTML伪DOC法: Microsoft Word 兼容打开 MHTML (.mht) 文件。MHTML是一种网页存档格式,它允许将HTML页面中的外部资源(如图片、CSS、JS)全部转换为Base64编码并封装在单一文件中。Word能完美渲染此类结构并保持排版。
核心实现逻辑
本方案的核心在于构建一个虚拟的 MHTML 容器。操作流程如下:
第一步:分析HTML并提取资源
获取传入的HTML字符串后,首先需要通过正则表达式过滤掉不需要导出的超链接(可选),并精准提取所有 <img> 标签中的 src 属性。若图片路径为相对路径,需结合给定的绝对根路径拼接成完整的URL,以便后续下载。
第二步:获取图片并编码
对于每一个提取到的图片URL,使用 file_get_contents 获取二进制流。由于MHTML规范要求资源部分使用Base64编码,因此需调用 base64_encode 进行转换。同时,根据文件扩展名(jpg, png等)确定正确的 MIME Type。
第三步:组装MHTML结构
MHTML文件本质上是一个遵循 RFC 1521 的多部分(multipart)MIME消息。我们需要设置:
- Content-Type: multipart/related
- 定义唯一的分隔符(Boundary)
组装顺序通常为:
1. 头部信息
2. 第一个实体:主HTML内容
3. 后续实体:各个内嵌的图片资源
4. 结束标记
第四步:输出下载
生成的字符串数据可以直接写入服务器文件,或通过HTTP响应头发送给浏览器触发下载:
header("Content-Type: application/msword");
header("Content-Disposition: attachment; filename=\"export.doc\";");
echo $mhtmlContent;
完整代码实现
以下是核心的 PHP 实现代码。主要包含两个部分:用于处理逻辑的 getWordDocument 函数和用于构建MHTML格式的 MhtFileMaker 类。
1. 获取Word文档内容的辅助函数
此函数负责解析HTML、清理链接、下载图片并委托给MhtFileMaker类生成最终结果。
/**
* 根据HTML代码获取word文档内容
* 创建一个本质为mht的文档,该函数会分析文件内容并从远程下载页面中的图片资源
*
* @param string $content HTML内容
* @param string $absolutePath 网页的绝对路径。如果HTML内容里的图片路径为相对路径,那么就需要填写这个参数,来让该函数自动填补成绝对路径。这个参数最后需要以/结束
* @param bool $isEraseLink 是否去掉HTML内容中的链接
*/
function getWordDocument( $content , $absolutePath = "" , $isEraseLink = true ) {
$mht = new MhtFileMaker();
// 去掉链接 (可选)
if ($isEraseLink)
$content = preg_replace('/<a\s*.*?\s*>(\s*.*?\s*)<\/a>/i' , '$1' , $content);
$images = array();
$files = array();
$matches = array();
// 提取img标签中的src属性 (要求src值必须被引号包围)
if ( preg_match_all('/<img[.\n]*?src\s*?=\s*[\"\']([^"]+)[\"\'](.*?)\/>/i', $content, $matches ) ) {
$arrPath = $matches[1];
for ( $i=0; $i<count($arrPath); $i++ ) {
$path = $arrPath[$i];
$imgPath = trim( $path );
if ( $imgPath != "" ) {
$files[] = $imgPath;
// 判断是否为绝对路径
if( substr($imgPath,0,7) == 'http://') {
// 已是绝对链接,不加前缀
} else {
// 相对路径,拼接绝对路径
$imgPath = $absolutePath.$imgPath;
}
$images[] = $imgPath;
}
}
}
// 添加主HTML内容
$mht->AddContents("tmp.html", $mht->GetMimeType("tmp.html"), $content);
// 遍历并添加图片资源
for ( $i=0; $i<count($images); $i++ ) {
$image = $images[$i];
// 尝试打开图片流
if ( @fopen($image , 'r') ) {
$imgcontent = @file_get_contents( $image );
if ( $imgcontent ) {
$mht->AddContents($files[$i], $mht->GetMimeType($image), $imgcontent);
}
} else {
// 记录错误或忽略
echo "file:".$image." not exist!<br />";
}
}
return $mht->GetFile();
}
2. MHT 文件生成类 (MhtFileMaker)
该类负责严格按照RFC协议规范,构建多部分MIME消息。
?php
class MhtFileMaker{
var $config = array();
var $headers = array();
var $headers_exists = array();
var $files = array();
var $boundary;
var $dir_base;
var $page_first;
function MhtFile($config = array()){
// 初始化配置
}
function SetHeader($header){
$this->headers[] = $header;
$key = strtolower(substr($header, 0, strpos($header, ':')));
$this->headers_exists[$key] = TRUE;
}
function SetFrom($from){
$this->SetHeader("From: $from");
}
function SetSubject($subject){
$this->SetHeader("Subject: $subject");
}
function SetDate($date = NULL, $istimestamp = FALSE){
if ($date == NULL) {
$date = time();
}
if ($istimestamp == TRUE) {
$date = date('D, d M Y H:i:s O', $date);
}
$this->SetHeader("Date: $date");
}
function SetBoundary($boundary = NULL){
if ($boundary == NULL) {
$this->boundary = '--' . strtoupper(md5(mt_rand())) . '_MULTIPART_MIXED';
} else {
$this->boundary = $boundary;
}
}
function SetBaseDir($dir){
$this->dir_base = str_replace("\\", "/", realpath($dir));
}
function SetFirstPage($filename){
$this->page_first = str_replace("\\", "/", realpath("{$this->dir_base}/$filename"));
}
// ... (此处省略内部私有辅助方法如 AutoAddFiles, AddDir 等,它们主要用于文件系统操作) ...
/**
* 添加文件内容到集合中
*/
function AddContents($filepath, $mimetype, $filecont, $encoding = NULL){
if ($encoding == NULL) {
// 默认使用 Base64 编码
$filecont = chunk_split(base64_encode($filecont), 76);
$encoding = 'base64';
}
$this->files[] = array(
'filepath' => $filepath,
'mimetype' => $mimetype,
'filecont' => $filecont,
'encoding' => $encoding
);
}
/**
* 检查必要的 Headers 和 Boundary
*/
function CheckHeaders(){
if (!array_key_exists('date', $this->headers_exists)) {
$this->SetDate(NULL, TRUE);
}
if ($this->boundary == NULL) {
$this->SetBoundary();
}
}
function GetMimeType($filename){
$pathinfo = pathinfo($filename);
switch ($pathinfo['extension']) {
case 'htm': case 'html': return 'text/html'; break;
case 'txt': case 'cgi': case 'php': return 'text/plain'; break;
case 'css': return 'text/css'; break;
case 'jpg': case 'jpeg': case 'jpe': return 'image/jpeg'; break;
case 'gif': return 'image/gif'; break;
case 'png': return 'image/png'; break;
default: return 'application/octet-stream'; break;
}
}
/**
* 生成最终的 MHT 字符串
*/
function GetFile(){
$this->CheckHeaders();
$contents = implode("\r\n", $this->headers);
$contents .= "\r\n";
$contents .= "MIME-Version: 1.0\r\n";
$contents .= "Content-Type: multipart/related;\r\n";
$contents .= "\tboundary=\"{$this->boundary}\";\r\n";
$contents .= "\ttype=\"" . $this->files[0]['mimetype'] . "\"\r\n";
$contents .= "X-MimeOLE: Produced By Mht File Maker v1.0 beta\r\n";
$contents .= "\r\n";
$contents .= "This is a multi-part message in MIME format.\r\n";
$contents .= "\r\n";
foreach ($this->files as $file) {
$contents .= "--{$this->boundary}\r\n";
$contents .= "Content-Type: $file[mimetype]\r\n";
$contents .= "Content-Transfer-Encoding: $file[encoding]\r\n";
$contents .= "Content-Location: $file[filepath]\r\n";
$contents .= "\r\n";
$contents .= $file['filecont'];
$contents .= "\r\n";
}
$contents .= "--{$this->boundary}--\r\n";
return $contents;
}
}
?>
使用方法与注意事项
在实际使用中,只需确保 MhtFileMaker 类已加载,然后调用核心函数即可:
// $content 是你的 HTML 源代码
// 第二个参数是图片所在的基础URL,确保相对路径能转为绝对路径
$fileContent = getWordDocument($content, "https://example.com/images/");
// 保存为文件
$fp = fopen("test.doc", 'w');
fwrite($fp, $fileContent);
fclose($fp);
排错提示:
- 图片不显示:最常见原因是 HTML 中的图片路径无法被访问(防火墙拦截、非公网地址)或路径未正确拼接。请检查
fopen是否能成功获取图片流。 - HTML 标签闭合问题:正则表达式提取 img 标签时,假设
src的值是被引号包裹的。如果源码中存在不规范写法(如<img src=img.png>),可能需要先清洗 HTML。 - 大文件内存溢出:如果 HTML 中包含大量高清大图,将所有图片 Base64 编码后会导致内存激增。建议在测试环境验证图片数量,必要时增加
memory_limit或对图片进行压缩预处理。
总结
利用 MHTML 特性配合 PHP 原生函数,是一种轻量级、无依赖的 Word 文档生成方案。它巧妙地规避了 Office COM 组件的性能瓶颈和跨平台限制,实现了图文混排的完美导出。对于简单的报表生成、报告导出需求,这套代码提供了稳定可靠的底层支持。 以上就是PHP导出含图片Word文档的MHT原理与实现的详细内容,更多关于PHP Word导出技巧的资料请关注本站其它相关文章!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













