3

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

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

因此,我编写了一个简短的脚本来解压缩 ODT 文件,修改 content.xml,然后再次压缩文件夹。你可以在下面看到完整的代码。

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

我需要注意什么特殊要求吗?除了 content.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(...)可以正常工作(

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

4

1 回答 1

3

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;
}

我不知道是否打算过滤掉.and ..,无论如何它没有起到作用。由于您包含..,它与realpath()转换为父目录一起,因此您正在破坏.odt文件。

2.所有目录分隔符必须是正斜杠(unix风格)

在 Windows 上,目录分隔符是反斜杠类型 ( \)。这解释了为什么您的脚本可以在 linux 上运行(由用户 CrazySabbath 测试),但不能在 Windows (XAMPP) 上运行。根据我在开头提到的错误报告,您必须使用正斜杠 ( /) 作为目录分隔符,以便 LibreOffice 打开文件。

另请注意,realpath()在 windows 上会将 unix 样式路径更改为 windows 样式。

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

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

3. DIRECTORY_SEPARATOR 不是必须的

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

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

于 2013-12-20T05:50:05.643 回答