php – 不支持交叉引用流

我是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仍然不是那么好,所以也许有更好的方法来做到这一点,我不知道.

我希望这有点帮助.我知道它给了我一些想法.如果您有任何其他具体问题,请告诉我.

相关文章

统一支付是JSAPI/NATIVE/APP各种支付场景下生成支付订单,返...
统一支付是JSAPI/NATIVE/APP各种支付场景下生成支付订单,返...
前言 之前做了微信登录,所以总结一下微信授权登录并获取用户...
FastAdmin是我第一个接触的后台管理系统框架。FastAdmin是一...
之前公司需要一个内部的通讯软件,就叫我做一个。通讯软件嘛...
统一支付是JSAPI/NATIVE/APP各种支付场景下生成支付订单,返...