使用php基础方式实现word中表单处理
<?php
/**
* zipFile 类用于处理 .docx 文件的解压、修改和重新打包
*/
class zipFile
{
/** @var ZipArchive ZIP 文件对象 */
private $zipFile;
/** @var string 临时目录路径 */
private $tempDir;
/** @var string 嵌入的 Excel 文件临时目录路径 */
private $excelTempDir;
/** @var string 原始 .docx 文件路径 */
private $docxPath;
/**
* 构造函数
*
* @param string $docxPath .docx 文件路径
* @throws Exception 如果无法打开 .docx 文件
*/
public function __construct($docxPath)
{
$this->docxPath = $docxPath;
$this->zipFile = new ZipArchive();
if ($this->zipFile->open($docxPath) !== TRUE) {
throw new Exception("无法打开 .docx 文件: $docxPath");
}
$this->tempDir = sys_get_temp_dir() . '/docx_' . uniqid();
mkdir($this->tempDir, 0777, true);
// ✅ 正确命名:用于存放解压的 Excel 内容
$this->excelTempDir = $this->tempDir . '/embedded_excel';
}
/**
* 解压整个 .docx 到临时目录
* 并自动解压其中的 Workbook1.xlsx(如果存在)
*/
public function extract()
{
// 1. 解压 .docx 主文件
$this->zipFile->extractTo($this->tempDir);
$this->zipFile->close();
// echo "✅ .docx 已解压到: {$this->tempDir}\n";
// 2. 查找并解压嵌入的 Excel 文件
$embeddedXlsxPath = $this->tempDir . '/word/embeddings/Workbook1.xlsx';
if (!file_exists($embeddedXlsxPath)) {
echo "⚠️ 未找到嵌入的 Workbook1.xlsx\n";
return;
}
$excelZip = new ZipArchive();
if ($excelZip->open($embeddedXlsxPath) !== TRUE) {
throw new Exception("无法打开嵌入的 Workbook1.xlsx");
}
// 创建目录并解压 Excel 内容
mkdir($this->excelTempDir, 0777, true);
$excelZip->extractTo($this->excelTempDir);
$excelZip->close();
echo "✅ Workbook1.xlsx 已解压到: {$this->excelTempDir}\n";
}
/**
* 获取解压后的 sheet1.xml 路径
*
* @return string
*/
public function getSheet1Path()
{
$path = $this->excelTempDir . '/xl/worksheets/sheet1.xml';
if (!file_exists($path)) {
throw new Exception("未找到 sheet1.xml: $path");
}
return $path;
}
/**
* 获取 sharedStrings.xml 路径(用于字符串修改)
*
* @return string
*/
public function getSharedStringsPath()
{
$path = $this->excelTempDir . '/xl/sharedStrings.xml';
if (!file_exists($path)) {
throw new Exception("未找到 sharedStrings.xml");
}
return $path;
}
/**
* 获取文件内容(.docx 内任意文件)
*
* @param string $path 文件路径(相对于解压目录)
* @return string|null
*/
public function getFileContent($path)
{
$fullPath = $this->tempDir . '/' . ltrim($path, '/');
return file_exists($fullPath) ? file_get_contents($fullPath) : null;
}
/**
* 写入修改后的文件
*
* @param string $path 文件路径(相对于解压目录)
* @param string $content 文件内容
*/
public function putFileContent($path, $content)
{
$fullPath = $this->tempDir . '/' . ltrim($path, '/');
$dir = dirname($fullPath);
if (!is_dir($dir)) {
mkdir($dir, 0777, true);
}
file_put_contents($fullPath, $content);
}
/**
* 修改 Excel 单元格值(仅限数字)
*
* @param string $cell 单元格地址,如 'B2'
* @param mixed $newValue 新值
*/
public function modifyExcelCell($cell, $newValue)
{
// 1. 修改 Excel 数据 (sheet1.xml)
$sheetFile = $this->getSheet1Path();
// 使用 DOMDocument 替代 simplexml_load_file
$dom = new DOMDocument();
$dom->load($sheetFile);
if (!$dom) {
throw new Exception("无法加载 sheet1.xml");
}
$xpath = new DOMXPath($dom);
$xpath->registerNamespace('x', 'http://schemas.openxmlformats.org/spreadsheetml/2006/main');
$nodes = $xpath->query("//x:c[@r='$cell']");
if ($nodes->length == 0) {
throw new Exception("未找到单元格: $cell");
}
$cNode = $nodes->item(0);
$vNodes = $xpath->query(".//x:v", $cNode);
if ($vNodes->length == 0) {
throw new Exception("单元格 $cell 缺少 <v> 节点");
}
$vNode = $vNodes->item(0);
$oldValue = $vNode->nodeValue;
$vNode->nodeValue = $newValue;
$dom->save($sheetFile);
echo "✅ 已修改 Excel 单元格 $cell: $oldValue → $newValue\n";
// 2. 同步更新图表缓存
$this->updateChartCache($cell, $newValue);
}
//批量更新
public function updateDataAndChart($dataMap)
{
foreach ($dataMap as $cell => $value) {
$this->modifyExcelCell($cell, $value);
}
}
/**
* 更新 chart1.xml 中的缓存值(用于柱状图、折线图等)
*
* @param string $cell 单元格地址,如 'B2'
* @param mixed $newValue 新值
* @param string $chartXmlPath 图表文件路径,默认为 chart1.xml
*/
/**
* 根据单元格地址,在 chart1.xml 中找到对应的 <c:f> 并更新其 numCache
*
* @param string $cell 单元格地址,如 'B2'
* @param mixed $newValue 新值
*/
private function updateChartCache($cell, $newValue)
{
$chartPath = $this->tempDir . '/word/charts/chart1.xml';
if (!file_exists($chartPath)) {
echo "⚠️ 未找到 chart1.xml,跳过图表缓存更新\n";
return;
}
// 使用 DOMDocument 替代 simplexml_load_file
$dom = new DOMDocument();
$dom->load($chartPath);
if (!$dom) {
throw new Exception("无法加载 chart1.xml");
}
// 注册命名空间
$xpath = new DOMXPath($dom);
$xpath->registerNamespace('c', 'http://schemas.openxmlformats.org/drawingml/2006/chart');
$xpath->registerNamespace('a', 'http://schemas.openxmlformats.org/drawingml/2006/main');
// 构造目标引用,如:Sheet1!$B$2
$escapedCell = preg_replace('/([A-Z]+)/', '$1$', $cell); // B2 -> B$2
$targetRef = "Sheet1!\$$escapedCell"; // 注意:Sheet1!$B$2
// 查找 <c:f> 内容为 Sheet1!$B$2 的节点
$fNodes = $xpath->query("//c:f[text()='$targetRef']");
if ($fNodes->length == 0) {
echo "🔍 未在 chart1.xml 中找到引用 $targetRef\n";
return;
}
foreach ($fNodes as $fNode) {
// 找到父级 <c:ser> 或 <c:pt> 等结构
$parent = $fNode->parentNode;
// 查找对应的 <c:numCache>
$numCacheNodes = $xpath->query(".//c:numCache", $parent);
if ($numCacheNodes->length == 0) {
// echo "⚠️ 找到引用 $targetRef,但无 <c:numCache> 缓存\n";
continue;
}
$numCache = $numCacheNodes->item(0);
// 查找 <c:pt idx="0"> 下的 <c:v>
$ptNodes = $xpath->query(".//c:pt[@idx='0']/c:v", $numCache);
if ($ptNodes->length > 0) {
$ptNode = $ptNodes->item(0);
$oldValue = $ptNode->nodeValue;
$ptNode->nodeValue = $newValue;
echo "📊 已更新图表缓存 [$targetRef]: $oldValue → $newValue\n";
} else {
// 如果没有 pt,尝试创建(高级功能,可选)
echo "⚠️ 未找到 <c:pt> 节点,无法更新缓存: $targetRef\n";
}
}
// 保存修改
$dom->save($chartPath);
}
/**
* 重新打包为 .docx
*
* @param string $outputPath 输出文件路径
*/
public function pack($outputPath)
{
// 1. 先打包嵌入的 Excel,并自动清理临时目录
$this->repackEmbeddedWorkbook();
// 2. 再打包整个 .docx 文件
$newZip = new ZipArchive();
if ($newZip->open($outputPath, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== TRUE) {
throw new Exception("无法创建输出文件: $outputPath");
}
$files = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($this->tempDir, FilesystemIterator::SKIP_DOTS),
RecursiveIteratorIterator::LEAVES_ONLY
);
foreach ($files as $file) {
if (!$file->isDir()) {
$filePath = $file->getRealPath();
$relativePath = substr($filePath, strlen($this->tempDir) + 1);
$newZip->addFile($filePath, $relativePath);
}
}
$newZip->close();
echo "✅ 已打包为: $outputPath\n";
}
/**
* 将修改后的 embedded_excel/ 目录重新打包为 Workbook1.xlsx
* 并删除临时解压目录(确保中间文件不残留)
*/
private function repackEmbeddedWorkbook()
{
$embeddedXlsxPath = $this->tempDir . '/word/embeddings/Workbook1.xlsx';
// 如果没有 embedded_excel 目录,说明没有 Excel 或未解压
if (!is_dir($this->excelTempDir)) {
echo "⚠️ 无嵌入 Excel 临时目录,跳过打包 Workbook1.xlsx\n";
return;
}
// 删除旧的 Workbook1.xlsx(如果存在)
if (file_exists($embeddedXlsxPath)) {
unlink($embeddedXlsxPath);
}
// 创建新的 Zip 存档
$zip = new ZipArchive();
if ($zip->open($embeddedXlsxPath, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== TRUE) {
throw new Exception("无法创建 Workbook1.xlsx");
}
$files = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($this->excelTempDir, FilesystemIterator::SKIP_DOTS),
RecursiveIteratorIterator::LEAVES_ONLY
);
foreach ($files as $file) {
if (!$file->isDir()) {
$filePath = $file->getRealPath();
$relativePath = substr($filePath, strlen($this->excelTempDir) + 1);
$zip->addFile($filePath, $relativePath);
}
}
$zip->close();
echo "✅ Workbook1.xlsx 已重新打包\n";
// 🔥 关键修复:立即删除 embedded_excel 临时目录
$this->rrmdir($this->excelTempDir);
echo "🗑️ 已清理 Excel 临时目录: {$this->excelTempDir}\n";
}
/**
* 列出解压目录中的所有文件
*
* @return array 文件路径列表
*/
public function listFiles()
{
$files = [];
if (!is_dir($this->tempDir)) return $files;
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($this->tempDir, FilesystemIterator::SKIP_DOTS)
);
foreach ($iterator as $file) {
if ($file->isFile()) {
$relativePath = substr($file->getPathname(), strlen($this->tempDir) + 1);
$files[] = $relativePath;
}
}
return $files;
}
/**
* 清理临时文件
*/
public function cleanup()
{
if (is_dir($this->tempDir)) {
$this->rrmdir($this->tempDir);
}
}
/**
* 递归删除目录
*
* @param string $dir 目录路径
*/
private function rrmdir($dir)
{
$files = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS),
RecursiveIteratorIterator::CHILD_FIRST
);
foreach ($files as $fileinfo) {
$todo = ($fileinfo->isDir() ? 'rmdir' : 'unlink');
$todo($fileinfo->getRealPath());
}
rmdir($dir);
}
}
// ✅ 使用示例(修复后)
try {
$processor = new zipFile('表单.docx');
$processor->extract();
// ✅ 方法一:使用封装好的 modifyExcelCell
$processor->modifyExcelCell('B2', 500);
$processor->pack('output.docx');
$processor->cleanup();
} catch (Exception $e) {
echo "❌ 错误: " . $e->getMessage() . "\n";
}

6746

被折叠的 条评论
为什么被折叠?



