我是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,但它们都是保密的.
我只是想获取元数据,但我甚至无法加载文档.我开始使用框架,所以我不必创建自己的解析器.如果有一种更简单的方法可以做到这一点,或者如果有人可以对此有所了解,我会非常感激.
编辑:为了澄清,我已经尝试了链接文档页面中的两种方法.两者都不起作用.
解决方法:
我最终不得不为此创建自己的解析器.如果有人发现这个并且有任何关于我如何做的进一步的建议或问题,只需添加评论.
解
我不会上传整个代码,因为它非常冗长,非常混乱,而且效率低下.自从最初的帖子以来,我作为一名开发人员已经成长了一段时间,并且已经意味着要回去并再次采取行动.因此,我将使用这篇文章解释我所拥有的内容,指出我发现的一些问题和解决方案,并就如何提高效率做出一些评论.希望这会让你更容易,希望这会激励我做出一些改变.免责声明:自从我上次查看此代码已经有好几个月了,所以不要指望我记住一切.但是,我非常擅长记录我的代码和发现(一次),所以我不记得的主要是次要的.
我能告诉你的最重要的事情是查看原始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数据的最佳方法是下载记事本(虽然您可以使用任何记事本之类的程序)并打开PDF文件.您将看到的第一件事是PDF版本,在这种情况下为“%PDF-1.4”,然后是许多令人困惑的外观角色.忽略这一点,但请注意PDF版本.请注意上面示例中的“xpacket”标记,这是您每次要查找元数据时需要查找的内容.只需按Ctrl F查找“xmpMeta”,第一次出现的应该是您的元数据.注意事项:请勿尝试使用受密码保护的文档.一切都被混淆了,包括Meta,这也意味着PHP也无法读取它.我相信有一个选项可以允许在受密码保护的PDF中读取元数据,但我无法确定,也不知道它是否真的适用于PHP.
就像你可以用Ctrl F在记事本中找到元素一样,你可以在PHP中用fgets()和while循环做同样的事情.我没有做但可能是一个好主意的实现,是确定文档的哪一端开始.这在所有PDF版本之间并不普遍,但相同的版本似乎也是类似的.例如,在PDF 1.4中,它们似乎都更接近文档的底部,而在PDF 1.6中,它们更接近顶部.同样,您可以从第一行检查PDF版本.使用PHP读取文档应该非常简单,所以我将跳过这段代码.虽然,我会指出,一旦找到整个元数据就退出循环是一个好主意,因为这是一个非常强大的处理操作,因此您需要尽可能节省时间.我还建议一次只运行10-20个文件组,如果文件较大则少.设置缓存系统对我有很多超时错误的帮助.
在您获得字符串中的元数据后,您将需要稍微清理一下.您要做的第一件事是确保您的元数据在一个根节点中很好地包装,以便XML解析器可以读取它.有几个例子他们没有.解决此问题的最佳/最简单方法是添加一个通用包装器.我建议使用最常用的一个.对我来说,这是带有内部“rdf”包装的“xmpMeta”标签.确保每个元数据启动相同对于导航文档非常重要.可能有一种更好的方法可以做到这一点,但是这样做并且效率不高(至少现在,在我删除了两个循环之后).
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对你来说可能有点不同.这些只是我遇到的所有命名空间.注意:我遇到的问题是删除节点的顺序很重要.我不知道为什么,但它会从“xmpMM”中删除“xmp”,我会被一个“MM”命名空间所困扰.上面的代码似乎没有那个问题,所以我不确定它是否仍然是一个问题,但为了以防万一,要小心.无论哪种方式,它都不是很难修复,只需要PHP排序然后反转它. REGEX删除默认名称空间声明.我尝试了许多不同的方法来解决这个问题,但这是我能找到的唯一能够持续发挥作用的方法.可能有一种方法可以将这两个REGEX函数结合起来,但是当谈到REGEX时我完全迷失了,而我的尝试只是让它破了.我不知道为什么我再用XML删除名称空间.这似乎是我最近尝试清理这一点的尝试之一,但这是来自一个有效的解决方案,所以它不会受到伤害(至少不是功能).除了REGEX之外,第一位可能会被删除并替换为XML解决方案,但我还没有验证这一点.在将字符串加载到XML之前,仍然需要删除默认命名空间,因为XML解析器不将“xmlns”属性视为实际属性.命名空间版本“xmlns:$prefix”的唯一原因是因为它们不被视为“xmlns”属性而是“xmlns:$prefix”属性.微妙之处.
不要像我一样.不要尝试实现有史以来创建的每个版本的PDF.它无法完成.嗯……它可能会,但它比它的价值更麻烦.幸运的是,这些都是内部文件,所以当我达到极限并且厌倦了调整它只是为了打破别的东西,或者失去我以前的兼容性时,我只是将最后几个文件转换成了.找到最常见的版本并处理它们,然后是下一个最常见的版本和设置条件,依此类推.一旦你到达只剩下一些的地方,让它们更新,或者只是宣布你不支持这个版本.特别是如果他们年纪大了.添加功能只是为了只用于少量文档而没有任何意义.我记得的一个重要问题是“xpacket”并不总是在自己的路线上.有时它与一些元数据标签共享空间.这导致“丢失”数据,因为直到找到“xpacket”之后我才开始记录元数据.这似乎是一个简单的修复,但它发现了很多问题,所以我最终完全废弃了这个版本并让它们更新.幸运的是那些是最后3-4个文件.
清理元数据后,就可以将其解析为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仍然不是那么好,所以也许有更好的方法来做到这一点,我不知道.
我希望这有点帮助.我知道它给了我一些想法.如果您有任何其他具体问题,请告诉我.
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。