【问题标题】:Cross-reference streams are not supported yet尚不支持交叉引用流
【发布时间】:2012-02-22 17:50:39
【问题描述】:

我是 Zend 框架的新手,所以如果我遗漏了一些简单的东西,我深表歉意。但是,我原以为直接取自 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。我会上传 PDF,但它们都是机密的。

我只是想获取元数据,但我什至无法加载文档。我开始使用框架,因此我不必创建自己的解析器。如果有更简单的方法可以做到这一点,或者如果有人可以对此有所了解,我将非常感激。

编辑:为澄清起见,我已经尝试了链接文档页面中的两种方法。两者都不起作用。

【问题讨论】:

  • 请告诉我们更多关于您的设置并发布实际代码。
  • 我确实发布了实际代码,它的逐字逐句来自documentation 直接从那里复制粘贴。您想了解有关设置的哪些信息?

标签: php zend-framework pdf zend-pdf


【解决方案1】:

我最终不得不为此创建自己的解析器。如果有人发现此内容并对我的操作方式有任何进一步的建议或问题,请添加评论。

解决方案

我不会上传整个代码,因为它真的很长、很乱而且效率很低。自从最初的帖子以来,我作为一名开发人员已经成长了一些,并且一直打算回去再试一次。所以我会用这篇文章来解释我所拥有的,指出我发现的一些问题和解决方案,以及制作一些关于如何提高效率的cmets。希望这会让你更容易,并希望这会激励我做出一些改变。 免责声明:自从我上次查看此代码以来已经有几个月了,所以不要指望我会记住所有内容。但是,我很擅长(一次)记录我的代码和发现,所以我不记得的大部分都是次要的。

我可以告诉您的最重要的事情是查看原始 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 版本,在这种情况下为“%PDF-1.4”,然后是许多看起来令人困惑的字符。忽略这一点,但请注意 PDF 版本。请注意上面示例中的“xpacket”标签,这是您每次想要查找元数据时都需要查找的标签。只需 Ctrl+F 即可找到“xmpmeta”,第一次出现的应该是您的元数据。 注意事项:不要尝试使用受密码保护的文档。一切都被混淆了,包括元数据,这也意味着 PHP 也无法读取它。我相信有一个选项可以允许读取受密码保护的 PDF 中的元数据,但我无法确定,也不知道它是否真的适用于 PHP。

正如您可以在 notepad++ 中使用 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,所以也许有更好的方法来做到这一点,我不知道。

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

【讨论】:

  • 感谢您分享您的解决方案。
  • @PrasadRajapaksha:更新了答案。不是完整的代码,而是一个好的开始和解释。不包含完整代码的原因是大小、效率以及对我的需求过于具体的可能性。
【解决方案2】:

我的情况是,当我将 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

【讨论】:

  • 换句话说:您将(不支持的)交叉引用流替换为(支持的)交叉引用表(以及其他更改)。
  • 它删除了输入字段
【解决方案3】:

我在使用 OpenOffice Writer 的导出到 PDF 功能生成的 PDF 时遇到了同样的问题。在 Acrobat 或其他 PDF 阅读器中,它们可以毫无问题地打开,但 ZF 无法处理它们。 我将 OpenOffice 文件保存为 .docs 并使用 MS Word 将它们导出为 .pdf。现在它们显示出来了...

【讨论】:

  • 多年来使用了一些不同的程序来制作它们,但现在主要的一个似乎是 Framemaker。不过,我认为从未使用过 OO。
【解决方案4】:

我在使用 adobe 创建的 pdf 文档时遇到了同样的问题。

这次我再次重新保存了文档,而不是使用 adobe 的标准保存选项。这次我使用“优化的 PDF”(另存为下的另一个 adobe 预设)保存为文档。

现在zend可以打开文件并且工作正常。

我不太确定预设中的哪些选项不同,但我认为这是 zend 无法处理的某种流式传输/分割的网络版本。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-06-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-31
    • 2022-10-17
    相关资源
    最近更新 更多