我是Zend Framework的新手,所以如果我遗漏了一些简单的话,我会道歉。但是,我原以为直接从documentation获取的代码会起作用。相反,我得到了一个未被捕获的例外。
Fatal error: Uncaught exception 'Zend_Pdf_Exception' with message 'Cross-reference streams are not supported yet.' in C:\xampp\php\zend\library\Zend\Pdf\Parser.php:318
Stack trace:
#0 C:\xampp\php\zend\library\Zend\Pdf\Parser.php(460): Zend_Pdf_Parser->_loadXRefTable('116')
#1 C:\xampp\php\zend\library\Zend\Pdf.php(318): Zend_Pdf_Parser->__construct('PDF/Current...', Object(Zend_Pdf_ElementFactory_Proxy), true)
#2 C:\xampp\php\zend\library\Zend\Pdf.php(267): Zend_Pdf->__construct('PDF/Current...', NULL, true)
#3 C:\xampp\htdocs\test\test.php(7): Zend_Pdf::load('PDF/Current...')
#4 {main}
thrown in C:\xampp\php\zend\library\Zend\Pdf\Parser.php on line 318
我一直在四处寻找可能的解决方案,但运气不佳。 This是最相似的,它无法解决我的问题。从我在那里阅读,以及从其他来源,PDF版本1.4及更早版本应该工作正常,但这不是这里的情况,它已经岁月。我的PDF版本都是1.4,所以我甚至不确定该帖子的准确程度。该代码适用于演示中包含的PDF,但不适用于我正在尝试使用的任何现有代码。我会上传PDF,但它们都是保密的。
我只是想获取元数据,但我甚至无法加载文档。我开始使用框架,所以我不必创建自己的解析器。如果有一种更简单的方法可以做到这一点,或者有人可以对此有所了解,我将非常感激。
编辑:为了澄清,我已经尝试了链接文档页面中的两种方法。两者都不起作用。
答案 0 :(得分:4)
我最终必须为此创建自己的解析器。如果有人发现这个并且有任何关于我如何做的进一步的建议或问题,只需添加评论。
<强>解决方案强>
我不会上传整个代码,因为它非常冗长,非常混乱,而且效率低下。自从最初的帖子以来,我作为一名开发人员已经成长了一段时间,并且已经意味着要回去再接再厉。因此,我将使用这篇文章解释我所拥有的内容,指出我发现的一些问题和解决方案,并就如何提高效率做出一些评论。希望这会让你更容易,希望这会激励我做出一些改变。 免责声明:自从我上次查看此代码以来已经有好几个月了,所以不要指望我记住一切。但是,我非常擅长记录我的代码和发现(一次),所以我不记得的主要是次要的。
我能告诉你的最重要的事情是查看原始XML,记笔记并比较一些文件。 Adobe在创建元数据语法时显然无法下定决心,因此您最终必须为所有不同的修订添加多个检查(我稍后会给出一个示例)。实际上,在文档中查找元数据非常简单。 Adobe为您提供了一组很好的开始/结束标记,因此您只需迭代文档直到找到它们。这是我正在解析的一个PDF的清理和通用样本。
<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?>
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 4.2.1-c043 52.372728, 2009/01/18-15:08:04 ">
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<rdf:Description rdf:about=""
xmlns:dc="http://purl.org/dc/elements/1.1/">
<dc:format>application/pdf</dc:format>
<dc:title>
<rdf:Alt>
<rdf:li xml:lang="x-default">Title of Document</rdf:li>
</rdf:Alt>
</dc:title>
<dc:creator>
<rdf:Seq>
<rdf:li>Creator of Document (Not author)</rdf:li>
</rdf:Seq>
</dc:creator>
<dc:description>
<rdf:Alt>
<rdf:li xml:lang="x-default">Short description</rdf:li>
</rdf:Alt>
</dc:description>
</rdf:Description>
<rdf:Description rdf:about=""
xmlns:xmp="http://ns.adobe.com/xap/1.0/">
<xmp:CreateDate>2004-01-27T16:36:09Z</xmp:CreateDate>
<xmp:CreatorTool>FrameMaker 7.0</xmp:CreatorTool>
<xmp:ModifyDate>2012-02-20T15:55:19Z</xmp:ModifyDate>
</rdf:Description>
<rdf:Description rdf:about=""
xmlns:pdf="http://ns.adobe.com/pdf/1.3/">
<pdf:Producer>Acrobat Distiller 9.4.5 (Windows)</pdf:Producer>
</rdf:Description>
<rdf:Description rdf:about=""
xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/">
<xmpMM:DocumentID>uuid:4eae0fcf-f493-4773-9473-f81c7491e8aa</xmpMM:DocumentID>
<xmpMM:InstanceID>uuid:98209926-ba98-4ac7-a5f7-050050048f5d</xmpMM:InstanceID>
</rdf:Description>
</rdf:RDF>
</x:xmpmeta>
<?xpacket end="w"?>
查看原始XML数据的最佳方法是下载notepad ++(尽管您可以使用任何类似程序的记事本)并打开PDF中的内容。您将看到的第一件事是PDF版本,&#34;%PDF-1.4&#34;在这种情况下,然后很多令人困惑的看起来字符。忽略这一点,但请注意PDF版本。请注意&#34; xpacket&#34;上面示例中的标记,表示每次要查找元数据时需要查找的内容。只需按Ctrl + F查找&#34; xmpmeta&#34;,第一次出现应该是您的元数据。 提醒:请勿尝试使用受密码保护的文档。一切都是混淆的,包括meta,这也意味着PHP也无法读取它。我相信有一个选项允许在受密码保护的PDF中读取元数据,但我无法记住,也不知道它是否真的适用于PHP。
就像你可以在Ctrl + F中找到notepad ++中的元素一样,你可以在PHP中用fgets()
和while循环做同样的事情。我没有做但可能是一个好主意的实现,是确定文档的哪一端开始。这在所有PDF版本之间并不通用,但相同的版本似乎也是类似的。例如,在PDF 1.4中,它们似乎都更接近文档的底部,而在PDF 1.6中,它们更接近顶部。同样,您可以从第一行检查PDF版本。使用PHP读取文档应该很容易设置,所以我将跳过这段代码。虽然,我会指出,一旦找到整个元数据就退出循环是一个好主意,因为这是一个非常强大的处理操作,因此您希望尽可能节省时间。我还建议一次只运行10-20个文件组,如果文件较大则少。设置缓存系统对我来说有很多超时错误。
在您获得字符串中的元数据后,您将要稍微清理它。您要做的第一件事是确保您的元数据在一个根节点中很好地包装,以便XML解析器可以读取它。有几个例子他们不是。解决此问题的最佳/最简单方法是添加一个通用包装器。我建议使用最常用的一个。对我来说,这就是&#34; xmpmeta&#34;标签内部&#34; rdf&#34;包装。确保每个元数据启动相同对于导航文档非常重要。可能有一种更好的方法可以做到这一点,但这样做效果并不太低效(至少现在,在我删除了两个循环之后)。
if(strpos($xmlstr, 'xmpmeta') === FALSE) {
if(strpos($xmlstr, 'rdf:rdf') === FALSE) { $xmlstr = "<rdf>$xmlstr</rdf>"; }
$xmlstr = "<xmpmeta>$xmlstr</xmpmeta>";
}
之后您将要删除命名空间。我尝试过使用它们,但是当每个实现中的URL不断变化并且您不确定自己拥有哪些URL时,它很难这样做。此外,它已经开始运行缓慢并添加所有额外的XML解析只会使它变得更糟。删除它们要简单得多。
$nodesToRemove = array('rdf', 'pdf', 'xap', 'xapMM', 'xmp', 'xmpMM', 'dc', 'x');
foreach($nodesToRemove as $remove) { $xmlstr = str_replace("$remove:", '', $xmlstr); }
$xmlstr = preg_replace('/xmlns[^=]*="[^"]*"/i', '', $xmlstr);
$xmlstr = preg_replace("/xmlns[^=]*='[^']*'/i", '', $xmlstr);
$dom = new DOMDocument();
$dom->loadXML($xmlstr);
$sxe = simplexml_import_dom($dom);
$root = $dom->documentElement;
$namespaces = $sxe->getDocNamespaces(TRUE);
foreach($namespaces as $prefix => $uri) {
$root->removeAttributeNS($uri, $prefix);
$root->removeAttribute("xmlns:$prefix");
}
if($root->hasChildNodes()) {
foreach($root->childNodes as $element) {
if ($element->nodeType != XML_TEXT_NODE) {
$this->_removeNS($element, $namespaces);
}
}
}
$nodesToRemove
对您来说可能略有不同。这些只是我遇到的所有命名空间。 注意:我遇到的问题是删除节点的顺序非常重要。我不知道为什么,但它会删除&#34; xmp&#34;来自&#34; xmpMM&#34;我会被一个&#34; MM&#34;命名空间。上面的代码似乎没有这个问题,所以我不确定它是否仍然是一个问题,但为了以防万一,要小心。无论哪种方式,它都难以修复,只需让PHP对其进行排序然后将其反转即可。 REGEX删除默认名称空间声明。我尝试了许多不同的方法来解决这个问题,但这是我能找到的唯一能够持续发挥作用的方法。可能有一种方法可以将这两个REGEX功能结合起来,但是当涉及到REGEX时我完全迷失了,而我的尝试只是让它破了。我不确定为什么然后再用XML删除名称空间。这似乎是我最近尝试清理这一点的尝试之一,但这是来自一个有效的解决方案,所以它不会受到伤害(至少不是功能)。除了REGEX之外,第一位可能会被删除并替换为XML解决方案,但我还没有对此进行验证。在将字符串加载到XML之前,仍然需要删除默认名称空间,因为XML解析器不考虑&#34; xmlns&#34;属性是一个实际属性。命名空间版本的唯一原因&#34; xmlns:$prefix
&#34;作品是因为它们不被认为是&#34; xmlns&#34;但是&#34; xmlns:$prefix
&#34;属性。微妙之处。
清理完元数据后,就可以将其解析为XML。例如,这是我如何获得描述。
function getDescription($xml) {
$return = 'Error: Metadata could not be retrieved';//Return value if metadata can not be parsed
$sxe = new SimpleXMLElement($xml);
$xpath = array(
'//description/Alt/li',
'//Description/Alt/li',
'//xmpmeta/RDF/*[last()]',
//'//Description/description',
);
foreach($xpath as $pattern) {
$temp = $sxe->xpath($pattern);
if( ! empty($temp)) {
$return = isset($temp[0]->description) ? $temp[0]->description : $temp[0];
break;
}
}
//Return value if description was not found in metadata
return empty($return) ? 'Error: Metadata "description" could not be retrieved' : strval($return);
}
有一些事情需要注意。第一个是XPATH的数组。这些是我之前谈到的那些多种情况。您可能还会注意到已注释掉XPATH。那个我还在努力兼容,或已经放弃了。我不记得了,因为我已经有一段时间不得不看这个了,没有人抱怨错误。所以我假设它不是问题。需要注意的另一件事是这个ONE字段的偏差量。元数据发生了很大的变化,有时会还原。因此,您必须检查每个案例,确保没有其他偏差,然后添加可能已发生的任何其他条件。需要考虑的是根据版本保存单独的解析器然后加载适当的解析器,可以减少效率低下。现在回过头来看,或许更简单的方法是查找每个修订版的标准化文档,但最后我最终通过反复试验来完成这项工作。所以,虽然这对我有用,但可能会有一些我错过的东西,因为它在我的任何文档中都不是问题。另外需要注意的是修订版之间标签的相似程度。我不是,并且使用高级XPATH仍然不是那么好,所以也许有更好的方法来做到这一点,我不知道。
我希望这会有所帮助。我知道它给了我一些想法。如果您有任何其他具体问题,请告诉我。
答案 1 :(得分:2)
我遇到了与OpenOffice Writer导出到PDF函数生成的PDF相同的问题。在Acrobat或其他PDF阅读器中,它们可以毫无问题地打开,但ZF无法处理它们。 我将OpenOffice文件保存为.docs并使用MS Word将它们导出到.pdf。现在他们被显示......
答案 2 :(得分:0)
我用adobe创建的pdf文档遇到了同样的问题。
这次我再次重新保存文件,而不是使用adobe的标准保存选项。这次我保存为带有“优化PDF”的文档(另一个adobe预设保存为)。
现在zend可以打开文件,它运行正常。
我不太确定哪些选项在预设中有所不同,但我认为它是某种流式/分区式网络版本,而zend无法处理。
答案 3 :(得分:0)
以我为例,当我将PDF转换为1.4版(从1.6版)时,它起作用了。我从这里使用了命令:https://superuser.com/questions/25598/linux-pdf-version-converter
gs -sDEVICE=pdfwrite -dCompatibilityLevel=1.4 -dPDFSETTINGS=/screen -dNOPAUSE -dQUIET -dBATCH -sOutputFile=output.pdf input.pdf