Мне пришлось создать собственный парсер для этого. Если кто-то найдет это и у него есть какие-либо дополнительные предложения или вопросы о том, как я это сделал, просто добавьте комментарий.
Решение
Я не собираюсь загружать весь код, так как он действительно длинный, очень грязный и неэффективный. Я немного вырос как разработчик с самого первого поста и намеревался вернуться и сделать еще один шаг. Поэтому я буду использовать этот пост, чтобы объяснить, что у меня есть, указать на некоторые проблемы и решения, которые я нашел, а также высказать некоторые замечания о том, как сделать его более эффективным. Надеюсь, это облегчит вам задачу, и, надеюсь, это вдохновит меня на некоторые изменения. Отказ от ответственности: Прошло несколько месяцев с тех пор, как я последний раз просматривал этот код, поэтому не ожидайте, что я запомню все. Тем не менее, я был довольно хорош в документировании своего кода и результатов (на этот раз), поэтому то, что я не помню, в основном незначительно.
Самая важная вещь, которую я могу вам сказать, это посмотреть на сырой 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.
Так же, как вы можете нажать Ctrl + F, чтобы найти мета в notepad ++, вы можете сделать то же самое в PHP с fgets()
и циклом while. Что-то, чего я не делал, но, вероятно, было бы хорошей идеей для реализации, - это определить, с какого конца документа начинать. Это не универсально для всех версий PDF, но одни и те же версии, похоже, размещаются одинаково. Например, в PDF 1.4 они кажутся ближе к нижней части документа, а в PDF 1.6 они ближе к верхней части. Опять же, вы можете проверить версию PDF из первой строки. Чтение документа с помощью PHP должно быть довольно простым в настройке, поэтому я собираюсь пропустить этот фрагмент кода. Тем не менее, я укажу, что это хорошая идея - выйти из цикла, как только вы найдете все метаданные, поскольку это очень трудоемкая операция, поэтому вы захотите сэкономить время там, где сможете. Я бы также предложил запускать это только для групп из 10-20 файлов за раз, реже для больших документов. Настройка системы кэширования очень помогла мне с ошибками тайм-аута.
После того как у вас есть метаданные в строке, вам нужно немного их почистить.Первое, что вы захотите сделать, это убедиться, что ваши метаданные аккуратно обернуты в один корневой узел, чтобы анализатор XML мог их прочитать.Была пара случаев, когда их не было.Лучший / самый простой способ исправить это - добавить обычную оболочку.Я бы предложил использовать самый распространенный из доступных вам.Для меня это был тег "xmpmeta" с внутренней оболочкой "rdf".Обеспечение того, чтобы все метаданные начинались одинаково, важно для навигации по документу.Возможно, есть лучший способ сделать это, но это работает и не слишком неэффективно (по крайней мере сейчас, после того, как я удалил две петли).
if(strpos($xmlstr, 'xmpmeta') === FALSE) {
if(strpos($xmlstr, 'rdf:rdf') === FALSE) { $xmlstr = "<rdf>$xmlstr</rdf>"; }
$xmlstr = "<xmpmeta>$xmlstr</xmpmeta>";
}
После этого вы захотите удалитьПространства имен.Я пытался их использовать, но это сложно сделать, когда 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
может быть немного другим для вас.Это просто все пространства имен, с которыми я столкнулся. Примечание: У меня были проблемы, из-за которых важен порядок удаления узлов.Я не уверен, почему, но это удалило бы «xmp» из «xmpMM», и я застрял бы с пространством имен «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.Это то, над чем я все еще работаю над совместимостью или отказался от него.Я не помню, прошло много времени с тех пор, как мне приходилось смотреть на это, и никто не жаловался на ошибки.Так что я предполагаю, что это не проблема.Еще одна вещь, на которую стоит обратить внимание, это количество отклонений только для этого ОДНОГО поля.Метаданные немного изменились, а иногда и возвращались.Таким образом, вы должны проверить для каждого случая, убедиться, что нет других отклонений, а затем добавить любые другие условия, которые могли возникнуть.Нужно было бы сохранить отдельные парсеры в зависимости от версии, а затем загрузить соответствующий парсер, что может снизить эффективность.Оглядываясь назад на это сейчас, возможно, проще было бы искать документы по стандартизации для каждой ревизии, но вместо этого я закончил делать это в основном методом проб и ошибок.Так что, хотя это работает для меня, могут быть некоторые вещи, которые я пропустил, потому что это не было проблемой ни в одном из моих документов.Еще одна вещь, которую стоит отметить, это то, насколько похожи теги между ревизиями.Я не был и все еще не так хорош с продвинутым XPATH, так что, может быть, есть лучший способ сделать это, я не знаю.
Надеюсь, это поможет.Я знаю, это дало мне несколько идей.Если у вас есть другие конкретные вопросы, дайте мне знать.