public function parse() { parent::parse(); // В файле обязан быть поток Current User. $cuStreamID = $this->getStreamIdByName("Current User"); if ($cuStreamID === false) { return false; } // Получаем этот поток, проверяем хеш (а перед нами ли PowerPoint-презентация?) // и читаем смещение до первой структуры UserEditAtom $cuStream = $this->getStreamById($cuStreamID); if ($this->getLong(12, $cuStream) == 4090610911.0) { return false; } $offsetToCurrentEdit = $this->getLong(16, $cuStream); // Находим в файле поток PowerPoint Document. $ppdStreamID = $this->getStreamIdByName("PowerPoint Document"); if ($ppdStreamID === false) { return false; } $ppdStream = $this->getStreamById($ppdStreamID); // В нём начинаем искать все UserEditAtom'ы, которые требуются нам для получения // смещений до PersistDirectory. $offsetLastEdit = $offsetToCurrentEdit; $persistDirEntry = array(); $live = null; $offsetPersistDirectory = array(); do { $userEditAtom = $this->getRecord($ppdStream, $offsetLastEdit, 0xff5); $live =& $userEditAtom; array_unshift($offsetPersistDirectory, $this->getLong(12, $userEditAtom)); $offsetLastEdit = $this->getLong(8, $userEditAtom); } while ($offsetLastEdit != 0x0); // Перебираем все полученные смещения. До этого здесь была *серьёзная* ошибка. for ($j = 0; $j < count($offsetPersistDirectory); $j++) { $rgPersistDirEntry = $this->getRecord($ppdStream, $offsetPersistDirectory[$j], 0x1772); if ($rgPersistDirEntry === false) { return false; } // Теперь читаем по четыре байта: первые 20 бит - это начальный ID вхождения в PersistDirectory, // следующие 12 - количество последующих смещений. for ($k = 0; $k < strlen($rgPersistDirEntry);) { $persist = $this->getLong($k, $rgPersistDirEntry); $persistId = $persist & 0xfffff; $cPersist = ($persist & 4293918720.0) >> 20 & 0xfff; $k += 4; // Заполняем массив PersistDirectory, исходя из полученных данных. for ($i = 0; $i < $cPersist; $i++) { $offset = $this->getLong($k + $i * 4, $rgPersistDirEntry); $persistDirEntry[$persistId + $i] = $this->getLong($k + $i * 4, $rgPersistDirEntry); } $k += $cPersist * 4; } } // В последней прочитанной записи ищем ID вхождения с DocumentContainer'ом. $docPersistIdRef = $this->getLong(16, $live); $documentContainer = $this->getRecord($ppdStream, $persistDirEntry[$docPersistIdRef], 0x3e8); // Теперь нам нужно пропустить много мусора до SlideList'а. $offset = 40 + 8; $exObjList = $this->getRecord($documentContainer, $offset, 0x409); if ($exObjList) { $offset += strlen($exObjList) + 8; } $documentTextInfo = $this->getRecord($documentContainer, $offset, 0x3f2); $offset += strlen($documentTextInfo) + 8; $soundCollection = $this->getRecord($documentContainer, $offset, 0x7e4); if ($soundCollection) { $offset += strlen($soundCollection) + 8; } $drawingGroup = $this->getRecord($documentContainer, $offset, 0x40b); $offset += strlen($drawingGroup) + 8; $masterList = $this->getRecord($documentContainer, $offset, 0xff0); $offset += strlen($masterList) + 8; $docInfoList = $this->getRecord($documentContainer, $offset, 0x7d0); if ($docInfoList) { $offset += strlen($docInfoList) + 8; } $slideHF = $this->getRecord($documentContainer, $offset, 0xfd9); if ($slideHF) { $offset += strlen($slideHF) + 8; } $notesHF = $this->getRecord($documentContainer, $offset, 0xfd9); if ($notesHF) { $offset += strlen($notesHF) + 8; } // Избавляемся от прочитанного мусора. unset($exObjList, $documentTextInfo, $soundCollection, $drawingGroup, $masterList, $docInfoList, $slideHF, $notesHF); // Читаем структуру SlideList. $slideList = $this->getRecord($documentContainer, $offset, 0xff0); $out = ""; for ($i = 0; $i < strlen($slideList);) { // Читаем текущий блок и определяем, что нам делать по его типу. $block = $this->getRecord($slideList, $i); switch ($this->getRecordType($slideList, $i)) { case 0x3f3: # RT_SlidePersistAtom // Вариант худший, если перед нами указатель на слайд, тогда мы должны // обратиться к PersistDirectory для получения этого слайда. $pid = $this->getLong(0, $block); $slide = $this->getRecord($ppdStream, @$persistDirEntry[$pid], 0x3ee); // Опять пропускаем всякое-разное до структуры Drawing. $offset = 32; $slideShowSlideInfoAtom = $this->getRecord($slide, $offset, 0x3f9); if ($slideShowSlideInfoAtom) { $offset += strlen($slideShowSlideInfoAtom) + 8; } $perSlideHFContainer = $this->getRecord($slide, $offset, 0xfd9); if ($perSlideHFContainer) { $offset += strlen($perSlideHFContainer) + 8; } $rtSlideSyncInfo12 = $this->getRecord($slide, $offset, 0x3714); if ($rtSlideSyncInfo12) { $offset += strlen($rtSlideSyncInfo12) + 8; } // Drawing - это объект MS Drawing, который имеет подобную PPT заголовочную структуру. // Чтобы не разбирать все возможные вложения структур одна в другую, поищем текст напрямую. $drawing = $this->getRecord($slide, $offset, 0x40c); $from = 0; while (preg_match("#(�|�)#", $drawing, $pocket, PREG_OFFSET_CAPTURE, $from)) { $pocket = @$pocket[1]; // Обязательно проверим, что заголовок блока начинается с двух "нулей", иначе возможно мы // нашли что-то в середине других данных. if (substr($drawing, $pocket[1] - 2, 2) == "") { // Читаем либо Plain текст, либо Unicode. if (ord($pocket[0]) == 0xa8) { $out .= htmlspecialchars($this->getRecord($drawing, $pocket[1] - 2, 0xfa8)) . " "; } else { $out .= $this->unicode_to_utf8($this->getRecord($drawing, $pocket[1] - 2, 0xfa0)) . " "; } } // Ищем следующее вхождение $from = $pocket[1] + 2; } break; case 0xfa0: # RT_TextCharsAtom // Варианты по проще: мы нашли Unicode-символьное вхождение $out .= $this->unicode_to_utf8($block) . " "; break; case 0xfa8: # RT_TextBytesAtom // Или обычный читый текст. $out .= htmlspecialchars($block) . " "; break; # some other skipped } // Сдвигаемся на длину блока с заголовком. $i += strlen($block) + 8; } // Возвращаем UTF-8 текст. return html_entity_decode(iconv("windows-1251", "utf-8", $out), ENT_QUOTES, "UTF-8"); }
public function parse() { parent::parse(); // Для чтения DOC'а нам нужны два потока - WordDocument и 0Table или // 1Table в зависимости от ситуации. Для начала найдћм первый - в нћм // (потоке) разбросаны кусочки текста, которые нам нужной поймать. $wdStreamID = $this->getStreamIdByName("WordDocument"); if ($wdStreamID === false) { return false; } // Поток нашли, читаем его в переменную $wdStream = $this->getStreamById($wdStreamID); // Далее нам нужно получить кое-что из FIB - специальный блок под названием // File Information Block в начале потока WordDocument. $bytes = $this->getShort(0xa, $wdStream); // Считываем какую именно таблицу нам нужно будет читать - первую или нулевую. // Для этого прочитаем один маленький бит из заголовка по известному смещению. $fWhichTblStm = ($bytes & 0x200) == 0x200; // Теперь нам нужно узнать позицию CLX в табличном потоке. Ну и размер этого самого // CLX - пусть ему пусто будет. $fcClx = $this->getLong(0x1a2, $wdStream); $lcbClx = $this->getLong(0x1a6, $wdStream); // Читаем несколько значений, чтобы отделить позиции от размерности в clx $ccpText = $this->getLong(0x4c, $wdStream); $ccpFtn = $this->getLong(0x50, $wdStream); $ccpHdd = $this->getLong(0x54, $wdStream); $ccpMcr = $this->getLong(0x58, $wdStream); $ccpAtn = $this->getLong(0x5c, $wdStream); $ccpEdn = $this->getLong(0x60, $wdStream); $ccpTxbx = $this->getLong(0x64, $wdStream); $ccpHdrTxbx = $this->getLong(0x68, $wdStream); // С помощью вышенайденных значений, находим значение последнего CP - character position $lastCP = $ccpFtn + $ccpHdd + $ccpMcr + $ccpAtn + $ccpEdn + $ccpTxbx + $ccpHdrTxbx; $lastCP += ($lastCP != 0) + $ccpText; // Находим в файле нужную нам табличку. $tStreamID = $this->getStreamIdByName(intval($fWhichTblStm) . "Table"); if ($tStreamID === false) { return false; } // И считываем из нећ поток в переменную $tStream = $this->getStreamById($tStreamID); // Потом находим в потоке CLX $clx = substr($tStream, $fcClx, $lcbClx); // А теперь нам в CLX (complex, ага) нужно найти кусок со смещениями и размерностями // кусочков текста. $lcbPieceTable = 0; $pieceTable = ""; // Отмечу, что здесь вааааааааааще жопа. В документации на сайте толком не сказано // сколько гона может быть до pieceTable в этом CLX, поэтому будем исходить из тупого // перебора - ищем возможное начало pieceTable (обязательно начинается на 0х02), затем // читаем следующие 4 байта - размерность pieceTable. Если размерность по факту и // размерность, записанная по смещению, то бинго! мы нашли нашу pieceTable. Нет? // ищем дальше. $from = 0; // Ищем 0х02 с текущего смещения в CLX while (($i = strpos($clx, chr(0x2), $from)) !== false) { // Находим размер pieceTable $lcbPieceTable = $this->getLong($i + 1, $clx); // Находим pieceTable $pieceTable = substr($clx, $i + 5); // Если размер фактический отличается от нужного, то это не то - // едем дальше. if (strlen($pieceTable) != $lcbPieceTable) { $from = $i + 1; continue; } // Хотя нет - вроде нашли, break, товарищи! break; } // Теперь заполняем массив character positions, пока не наткнћмся // на последний CP. $cp = array(); $i = 0; while (($cp[] = $this->getLong($i, $pieceTable)) != $lastCP) { $i += 4; } // Остаток идћт на PCD (piece descriptors) $pcd = str_split(substr($pieceTable, $i + 4), 8); $text = ""; // Ура! мы подошли к главному - чтение текста из файла. // Идћм по декскрипторам кусочков for ($i = 0; $i < count($pcd); $i++) { // Получаем слово со смещением и флагом компрессии $fcValue = $this->getLong(2, $pcd[$i]); // Смотрим - что перед нами тупой ANSI или Unicode $isANSI = ($fcValue & 0x40000000) == 0x40000000; // Остальное без макушки идћт на смещение $fc = $fcValue & 0x3fffffff; // Получаем длину кусочка текста $lcb = $cp[$i + 1] - $cp[$i]; // Если перед нами Unicode, то мы должны прочитать в два раза больше файлов if (!$isANSI) { $lcb *= 2; } else { $fc /= 2; } // Читаем кусок с учћтом смещения и размера из WordDocument-потока $part = substr($wdStream, $fc, $lcb); // Если перед нами Unicode, то преобразовываем его в нормальное состояние if (!$isANSI) { $part = $this->unicode_to_utf8($part); } // Добавляем кусочек к общему тексту $text .= $part; } // Удаляем из файла вхождения с внедрћнными объектами $text = preg_replace("/HYPER13 *(INCLUDEPICTURE|HTMLCONTROL)(.*)HYPER15/iU", "", $text); $text = preg_replace("/HYPER13(.*)HYPER14(.*)HYPER15/iU", "\$2", $text); // Возвращаем результат return $text; }
public function parse() { parent::parse(); $wdStreamID = $this->getStreamIdByName("WordDocument"); if ($wdStreamID === false) { return false; } $wdStream = $this->getStreamById($wdStreamID); $bytes = $this->getShort(0xa, $wdStream); $fWhichTblStm = ($bytes & 0x200) == 0x200; $fcClx = $this->getLong(0x1a2, $wdStream); $lcbClx = $this->getLong(0x1a6, $wdStream); $ccpText = $this->getLong(0x4c, $wdStream); $ccpFtn = $this->getLong(0x50, $wdStream); $ccpHdd = $this->getLong(0x54, $wdStream); $ccpMcr = $this->getLong(0x58, $wdStream); $ccpAtn = $this->getLong(0x5c, $wdStream); $ccpEdn = $this->getLong(0x60, $wdStream); $ccpTxbx = $this->getLong(0x64, $wdStream); $ccpHdrTxbx = $this->getLong(0x68, $wdStream); $lastCP = $ccpFtn + $ccpHdd + $ccpMcr + $ccpAtn + $ccpEdn + $ccpTxbx + $ccpHdrTxbx; $lastCP += ($lastCP != 0) + $ccpText; $tStreamID = $this->getStreamIdByName(intval($fWhichTblStm) . "Table"); if ($tStreamID === false) { return false; } $tStream = $this->getStreamById($tStreamID); $clx = substr($tStream, $fcClx, $lcbClx); $lcbPieceTable = 0; $pieceTable = ""; $from = 0; while (($i = strpos($clx, chr(0x2), $from)) !== false) { $lcbPieceTable = $this->getLong($i + 1, $clx); $pieceTable = substr($clx, $i + 5); if (strlen($pieceTable) != $lcbPieceTable) { $from = $i + 1; continue; } break; } $cp = array(); $i = 0; while (($cp[] = $this->getLong($i, $pieceTable)) != $lastCP) { $i += 4; } $pcd = str_split(substr($pieceTable, $i + 4), 8); $text = ""; for ($i = 0; $i < count($pcd); $i++) { $fcValue = $this->getLong(2, $pcd[$i]); $isANSI = ($fcValue & 0x40000000) == 0x40000000; $fc = $fcValue & 0x3fffffff; $lcb = $cp[$i + 1] - $cp[$i]; if (!$isANSI) { $lcb *= 2; } else { $fc /= 2; } $part = substr($wdStream, $fc, $lcb); if (!$isANSI) { $part = $this->unicode_to_utf8($part); } $text .= $part; } $text = preg_replace("/HYPER13 *(INCLUDEPICTURE|HTMLCONTROL)(.*)HYPER15/iU", "", $text); $text = preg_replace("/HYPER13(.*)HYPER14(.*)HYPER15/iU", "\$2", $text); return $text; }