使用 PHP 操作 ODT 文档(基本搜索和替换)


Manipulating ODT documents with PHP (basic search and replace)

使用LibreOffice,我设计并编写了一个文本文档(ODT格式(。现在我想以编程方式查找某些占位符,并将它们替换为数据库中的文本。

我知道有一些用于PHP的ODT库,但是由于ODT文件只是包含XML文件(以及其他文件(的ZIP文件,我认为这应该可以用基本的PHP来实现,没有任何库,不是吗?

所以我写了一个简短的脚本,用于解压缩 ODT 文件、修改内容.xml然后再次压缩文件夹。您可以在下面看到完整的代码。

虽然我可以手动解压缩、替换、压缩,但当我让下面的 PHP 脚本完成工作时,它不起作用。LibreOffice会告诉我它无法打开文档,它可以尝试修复它(这也不起作用(。

有什么特殊要求需要注意吗?除了内容之外,我是否必须修改任何元文件.xml?

if (unzipFolder('Template.odt', 'temp')) {
    $source = file_get_contents('temp'.DIRECTORY_SEPARATOR.'content.xml');
    $source = str_replace('XXXplaceholder1XXX', 'Example Value #1', $source);
    $source = str_replace('XXXplaceholder2XXX', 'Example Value #2', $source);
    file_put_contents('temp'.DIRECTORY_SEPARATOR.'content.xml', $source);
    zipFolder('temp', 'output/Document.odt');
}
function unzipFolder($zipInputFile, $outputFolder) {
    $zip = new ZipArchive;
    $res = $zip->open($zipInputFile);
    if ($res === true) {
        $zip->extractTo($outputFolder);
        $zip->close();
        return true;
    }
    else {
        return false;
    }
}
function zipFolder($inputFolder, $zipOutputFile) {
    if (!extension_loaded('zip') || !file_exists($inputFolder)) {
        return false;
    }
    $zip = new ZipArchive();
    if (!$zip->open($zipOutputFile, ZIPARCHIVE::CREATE)) {
        return false;
    }
    $inputFolder = str_replace('''', DIRECTORY_SEPARATOR, realpath($inputFolder));
    if (is_dir($inputFolder) === true) {
        $files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($inputFolder), RecursiveIteratorIterator::SELF_FIRST);
        foreach ($files as $file) {
            $file = str_replace('''', DIRECTORY_SEPARATOR, $file);
            if (in_array(substr($file, strrpos($file, '/')+1), array('.', '..'))) {
                continue;
            }
            $file = realpath($file);
            if (is_dir($file) === true) {
                $dirName = str_replace($inputFolder.DIRECTORY_SEPARATOR, '', $file.DIRECTORY_SEPARATOR);
                $zip->addEmptyDir($dirName);
            }
            else if (is_file($file) === true) {
                $fileName = str_replace($inputFolder.DIRECTORY_SEPARATOR, '', $file);
                $zip->addFromString($fileName, file_get_contents($file));
            }
        }
    }
    else if (is_file($inputFolder) === true) {
        $zip->addFromString(basename($inputFolder), file_get_contents($inputFolder));
    }
    return $zip->close();
}

编辑#1:如果您只是解压缩并重新压缩ODT文件的内容,即如果您取消注释所有数据操作,则上面的代码甚至不起作用。PHP 的 ZipArchive 输出格式有问题吗?

编辑#2:更具体地说,它是破坏一切的zipFolder(...)方法。你可以让PHP解压缩,字符串操作也可以正常工作(str_replace(...)(,但是当zipFolder(...)函数创建存档时,它无法打开,而如果你手动创建存档(例如,使用7-Zip(,它可以正常工作。

编辑#3:我什至通过exec(...)调用7-Zip来替换PHP中的重新压缩部分来使其工作。所以问题肯定是在这里创建一个合适的ZIP存档。为了更好的可移植性和更少的依赖关系,当然,如果PHP的解决方案ZipArchive有效并且我们不需要7-Zip,那就更好了。

zipFolder()函数存在许多问题,导致.odt文件损坏。LibreOffice中使用的文件加载器不是很宽容,这可能也适用于OpenOffice,因为前者是后者的分支。

感谢PHP错误报告#48763,我设法缩小了问题的范围。这个错误报告主要处理ZipArchive::addFromString()的问题,这个问题从PHP 5.2.11开始就已经修复了。然而,用户"Lars"提供了对LibreOffice文件加载器的限制的见解。

"当使用Windows文件系统分隔符时,.ods zip存档是 坏了,即使提取存档是有效的。

1. "."

和 ".." 仍包含在存档中

你有一个if语句,如下所示:

if (in_array(substr($file, strrpos($file, '/')+1), array('.', '..'))) {
    continue;
}

我不知道这样做的目的是过滤掉...,无论如何它没有解决问题。由于您包含 .. ,它与 realpath() 一起转换为父目录,因此您正在破坏.odt文件。

2. 所有目录分隔符必须是正斜杠(unix 样式(

在 Windows 上,目录分隔符为反斜杠类型 ( ' (。这就解释了为什么你的脚本在Linux上工作(由用户CrazySabbath测试(,而不是在Windows(XAMPP(上。根据我在开头提到的错误报告,您必须使用正斜杠(/(作为LibreOffice的目录分隔符才能打开文件。

另请注意,Windows 上的realpath()会将 unix 样式路径更改为窗口样式。

ZIP 文件标准规定所有斜杠都必须是正斜杠,但是似乎ZipArchive让您忽略标准,不为您进行转换。

4.4.17.1 文件的名称,带有可选的相对路径。 存储的路径不得包含驱动器或 设备字母或前导斜杠。 所有斜杠 必须是正斜杠"/",而不是 向后斜杠"''"以与 Amiga 兼容 和 UNIX 文件系统等。

3. DIRECTORY_SEPARATOR不是必需

你的代码不是问题,只是一个一般提示。不需要使用常量DIRECTORY_SEPARATOR,只需使用正斜杠(/(,它就可以在*nix和Windows系统上工作。

但是,DIRECTORY_SEPARATOR对于爆炸或替换路径之类的事情仍然有用。