function rtf2text($filename) { // Read the data from the input file. $text = file_get_contents($filename); if (!strlen($text)) { return ""; } // Create empty stack array. $document = ""; $stack = array(); $j = -1; // Read the data character-by- character… for ($i = 0, $len = strlen($text); $i < $len; $i++) { $c = $text[$i]; // Depending on current character select the further actions. switch ($c) { // the most important key word backslash case "\\": // read next character $nc = $text[$i + 1]; // If it is another backslash or nonbreaking space or hyphen, // then the character is plain text and add it to the output stream. if ($nc == '\\' && rtf_isPlainText($stack[$j])) { $document .= '\\'; } elseif ($nc == '~' && rtf_isPlainText($stack[$j])) { $document .= ' '; } elseif ($nc == '_' && rtf_isPlainText($stack[$j])) { $document .= '-'; } elseif ($nc == '*') { $stack[$j]["*"] = true; } elseif ($nc == "'") { $hex = substr($text, $i + 2, 2); if (rtf_isPlainText($stack[$j])) { $document .= html_entity_decode("&#" . hexdec($hex) . ";"); } //Shift the pointer. $i += 2; // Since, we’ve found the alphabetic character, the next characters are control word // and, possibly, some digit parameter. } elseif ($nc >= 'a' && $nc <= 'z' || $nc >= 'A' && $nc <= 'Z') { $word = ""; $param = null; // Start reading characters after the backslash. for ($k = $i + 1, $m = 0; $k < strlen($text); $k++, $m++) { $nc = $text[$k]; // If the current character is a letter and there were no digits before it, // then we’re still reading the control word. If there were digits, we should stop // since we reach the end of the control word. if ($nc >= 'a' && $nc <= 'z' || $nc >= 'A' && $nc <= 'Z') { if (empty($param)) { $word .= $nc; } else { break; } // If it is a digit, store the parameter. } elseif ($nc >= '0' && $nc <= '9') { $param .= $nc; } elseif ($nc == '-') { if (empty($param)) { $param .= $nc; } else { break; } } else { break; } } // Shift the pointer on the number of read characters. $i += $m - 1; // Start analyzing what we’ve read. We are interested mostly in control words. $toText = ""; switch (strtolower($word)) { // If the control word is "u", then its parameter is the decimal notation of the // Unicode character that should be added to the output stream. // We need to check whether the stack contains \ucN control word. If it does, // we should remove the N characters from the output stream. case "u": $toText .= html_entity_decode("&#x" . dechex($param) . ";"); $ucDelta = @$stack[$j]["uc"]; if ($ucDelta > 0) { $i += $ucDelta; } break; // Select line feeds, spaces and tabs. // Select line feeds, spaces and tabs. case "par": case "page": case "column": case "line": case "lbr": $toText .= "\n"; break; case "emspace": case "enspace": case "qmspace": $toText .= " "; break; case "tab": $toText .= "\t"; break; // Add current date and time instead of corresponding labels. // Add current date and time instead of corresponding labels. case "chdate": $toText .= date("m.d.Y"); break; case "chdpl": $toText .= date("l, j F Y"); break; case "chdpa": $toText .= date("D, j M Y"); break; case "chtime": $toText .= date("H:i:s"); break; // Replace some reserved characters to their html analogs. // Replace some reserved characters to their html analogs. case "emdash": $toText .= html_entity_decode("—"); break; case "endash": $toText .= html_entity_decode("–"); break; case "bullet": $toText .= html_entity_decode("•"); break; case "lquote": $toText .= html_entity_decode("‘"); break; case "rquote": $toText .= html_entity_decode("’"); break; case "ldblquote": $toText .= html_entity_decode("«"); break; case "rdblquote": $toText .= html_entity_decode("»"); break; // Add all other to the control words stack. If a control word // does not include parameters, set ¶m to true. // Add all other to the control words stack. If a control word // does not include parameters, set ¶m to true. default: $stack[$j][strtolower($word)] = empty($param) ? true : $param; break; } // Add data to the output stream if required. if (rtf_isPlainText($stack[$j])) { $document .= $toText; } } $i++; break; // If we read the opening brace {, then new subgroup starts and we add // new array stack element and write the data from previous stack element to it. // If we read the opening brace {, then new subgroup starts and we add // new array stack element and write the data from previous stack element to it. case "{": array_push($stack, $stack[$j++]); break; // If we read the closing brace }, then we reach the end of subgroup and should remove // the last stack element. // If we read the closing brace }, then we reach the end of subgroup and should remove // the last stack element. case "}": array_pop($stack); $j--; break; // Skip “trash”. // Skip “trash”. case '\\0': case '\\r': case '\\f': case '\\n': break; // Add other data to the output stream if required. // Add other data to the output stream if required. default: if (rtf_isPlainText($stack[$j])) { $document .= $c; } break; } } // Return result. return $document; }
$i++; break; case "{": array_push($stack, $stack[$j++]); break; case "}": array_pop($stack); $j--; break; case '\\0': case '\\r': case '\\f': case '\\n': break; default: if (rtf_isPlainText($stack[$j])) { $trueText .= $c; } break; } } $length = strlen($trueText); $size = $length; if ($size > 1000) { $prefix = "KB"; } if ($size > 1000000) { $prefix = "MB"; } if ($size > 1000000000) { $prefix = "GB";
function rtf2text($filename) { // Пытаемся прочить данные из переданного нам rtf-файла, в случае успеха - // продолжаем наше злобненькое дело. $text = file_get_contents($filename); if (!strlen($text)) { return ""; } # Speeding up via cutting binary data from large rtf's. if (strlen($text) > 1024 * 1024) { $text = preg_replace("#[\r\n]#", "", $text); $text = preg_replace("#[0-9a-f]{128,}#is", "", $text); } # For Unicode escaping $text = str_replace("\\'3f", "?", $text); $text = str_replace("\\'3F", "?", $text); // Итак, самое главное при чтении данных из rtf'а - это текущее состояние // стека модификаторов. Начинаем мы, естественно, с пустого стека и отрицательного // его (стека) уровня. $document = ""; $stack = array(); $j = -1; $fonts = array(); // Читаем посимвольно данные... for ($i = 0, $len = strlen($text); $i < $len; $i++) { $c = $text[$i]; // исходя из текущего символа выбираем, что мы с данными будем делать. switch ($c) { // итак, самый важный ключ "обратный слеш" case "\\": // читаем следующий символ, чтобы понять, что нам делать дальше $nc = $text[$i + 1]; // Если это другой бэкслеш, или неразрывный пробел, или обязательный // дефис, то мы вставляем соответствующие данные в выходной поток // (здесь и далее, в поток втавляем только в том случае, если перед // нами именно текст, а не шрифтовая вставка, к примеру). if ($nc == '\\' && rtf_isPlainText($stack[$j])) { $document .= '\\'; } elseif ($nc == '~' && rtf_isPlainText($stack[$j])) { $document .= ' '; } elseif ($nc == '_' && rtf_isPlainText($stack[$j])) { $document .= '-'; } elseif ($nc == '*') { $stack[$j]["*"] = true; } elseif ($nc == "'") { $hex = substr($text, $i + 2, 2); if (rtf_isPlainText($stack[$j])) { #echo $hex." "; #dump($stack[$j], false); #dump($fonts, false); if (!empty($stack[$j]["mac"]) || @$fonts[$stack[$j]["f"]] == 77) { $document .= from_macRoman(hexdec($hex)); } elseif (@$stack[$j]["ansicpg"] == "1251" || @$stack[$j]["lang"] == "1029") { $document .= chr(hexdec($hex)); } else { $document .= "&#" . hexdec($hex) . ";"; } } #dump($stack[$j], false); // Мы прочитали два лишних символа, должны сдвинуть указатель. $i += 2; // Так перед нами буква, а это значит, что за \ идёт упраляющее слово // и возможно некоторый циферный параметр, которые мы должны прочитать. } elseif ($nc >= 'a' && $nc <= 'z' || $nc >= 'A' && $nc <= 'Z') { $word = ""; $param = null; // Начинаем читать символы за бэкслешем. for ($k = $i + 1, $m = 0; $k < strlen($text); $k++, $m++) { $nc = $text[$k]; // Если текущий символ буква и до этого не было никаких цифр, // то мы всё ещё читаем управляющее слово, если же были цифры, // то по документации мы должны остановиться - ключевое слово // так или иначе закончилось. if ($nc >= 'a' && $nc <= 'z' || $nc >= 'A' && $nc <= 'Z') { if (empty($param)) { $word .= $nc; } else { break; } // Если перед нами цифра, то начинаем записывать параметр слова. } elseif ($nc >= '0' && $nc <= '9') { $param .= $nc; } elseif ($nc == '-') { if (empty($param)) { $param .= $nc; } else { break; } // В любом другом случае - конец. } else { break; } } // Сдвигаем указатель на количество прочитанных нами букв/цифр. $i += $m - 1; // Начинаем разбираться, что же мы такое начитали. Нас интересует // именно управляющее слово. $toText = ""; switch (strtolower($word)) { // Если слово "u", то параметр - это десятичное представление // unicode-символа, мы должны добавить его в выход. // Но мы должны учесть, что за символом может стоять его // замена, в случае, если программа просмотрщик не может работать // с Unicode, поэтому при наличии \ucN в стеке, мы должны откусить // "лишние" N символов из исходного потока. case "u": $toText .= html_entity_decode("&#x" . sprintf("%04x", $param) . ";"); $ucDelta = !empty($stack[$j]["uc"]) ? @$stack[$j]["uc"] : 1; /*for ($k = 1, $m = $i + 2; $k <= $ucDelta && $m < strlen($text); $k++, $m++) { $d = $text[$m]; if ($d == '\\') { $dd = $text[$m + 1]; if ($dd == "'") $m += 3; elseif($dd == '~' || $dd == '_') $m++; } } $i = $m - 2;*/ #$i += $m - 2; if ($ucDelta > 0) { $i += $ucDelta; } break; // Обработаем переводы строк, различные типы пробелов, а также символ // табуляции. // Обработаем переводы строк, различные типы пробелов, а также символ // табуляции. case "par": case "page": case "column": case "line": case "lbr": $toText .= "\n"; break; case "emspace": case "enspace": case "qmspace": $toText .= " "; break; case "tab": $toText .= "\t"; break; // Добавим вместо соответствующих меток текущие дату или время. // Добавим вместо соответствующих меток текущие дату или время. case "chdate": $toText .= date("m.d.Y"); break; case "chdpl": $toText .= date("l, j F Y"); break; case "chdpa": $toText .= date("D, j M Y"); break; case "chtime": $toText .= date("H:i:s"); break; // Заменим некоторые спецсимволы на их html-аналоги. // Заменим некоторые спецсимволы на их html-аналоги. case "emdash": $toText .= html_entity_decode("—"); break; case "endash": $toText .= html_entity_decode("–"); break; case "bullet": $toText .= html_entity_decode("•"); break; case "lquote": $toText .= html_entity_decode("‘"); break; case "rquote": $toText .= html_entity_decode("’"); break; case "ldblquote": $toText .= html_entity_decode("«"); break; case "rdblquote": $toText .= html_entity_decode("»"); break; # Skipping binary data... # Skipping binary data... case "bin": $i += $param; break; case "fcharset": $fonts[@$stack[$j]["f"]] = $param; break; // Всё остальное добавим в текущий стек управляющих слов. Если у текущего // слова нет параметров, то приравляем параметр true. // Всё остальное добавим в текущий стек управляющих слов. Если у текущего // слова нет параметров, то приравляем параметр true. default: $stack[$j][strtolower($word)] = empty($param) ? true : $param; break; } // Если что-то требуется вывести в выходной поток, то выводим, если это требуется. if (rtf_isPlainText($stack[$j])) { $document .= $toText; } } else { $document .= " "; } $i++; break; // Перед нами символ { - значит открывается новая подгруппа, поэтому мы должны завести // новый уровень стека с переносом значений с предыдущих уровней. // Перед нами символ { - значит открывается новая подгруппа, поэтому мы должны завести // новый уровень стека с переносом значений с предыдущих уровней. case "{": if ($j == -1) { $stack[++$j] = array(); } else { array_push($stack, $stack[$j++]); } break; // Закрывающаяся фигурная скобка, удаляем текущий уровень из стека. Группа закончилась. // Закрывающаяся фигурная скобка, удаляем текущий уровень из стека. Группа закончилась. case "}": array_pop($stack); $j--; break; // Всякие ненужности отбрасываем. // Всякие ненужности отбрасываем. case "": case "\r": case "\f": case "\\b": case "\t": break; // Остальное, если требуется, отправляем на выход. // Остальное, если требуется, отправляем на выход. case "\n": $document .= " "; break; default: if (rtf_isPlainText($stack[$j])) { $document .= $c; } break; } } // Возвращаем, что получили. return html_entity_decode(iconv("windows-1251", "utf-8", $document), ENT_QUOTES, "UTF-8"); }