php完整处理word中表单数据的方法

使用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";
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

huluang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值