使用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文件加载器的限制的见解。
1. ".""当使用Windows文件系统分隔符时,.ods zip存档是 坏了,即使提取存档是有效的。
和 ".." 仍包含在存档中
你有一个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
对于爆炸或替换路径之类的事情仍然有用。