PHP Text Extractor
Fejlesztő és karbantartó: IntrAgile Kft.
composer require intragile/php-text-extractor
Fejlesztő és karbantartó: IntrAgile Kft.
Szöveg kinyerése PDF-, Word-, Excel-, PowerPoint-, OpenDocument-, RTF-, HTML- és CSV-fájlokból, valamint képekből egyetlen hívással: keresőindexhez, RAG-pipeline-hoz, archiváláshoz.
English README → README.md · Laravel? → intragile/laravel-text-extractor
use IntrAgile\TextExtractor\TextExtractor;
$result = TextExtractor::create()->extract('/path/to/feltoltes.docx');
if ($result->isSuccessful()) {
$index->store($result->text); // '' ha a fájlban egyszerűen nincs szöveg
} elseif ($result->isRetryable()) {
$queue->retryLater(); // timeout, hiányzó eszköz, IO-hiba
} else {
$log->info($result->reason->value); // encrypted, corrupt, too_large, ...
}
- Nincs Composer-függőség. A DOCX-, XLSX-, PPTX-, ODT-, ODS- és ODP-fájlokat a PHP saját
ZipArchive- ésXMLReader-osztálya olvassa. A PDF-hez a szabványospdftotext, az OCR-hez atesseractkell. - Korlátos memória. Az Office-fájlokat a zipből streamelve, csomópontonként olvassa, soha nem tölti be egyben. A táblázatok szövegei és a kimenet felső határosak. A HTML-nek, amelyhez DOM kell, saját méretkorlátja van. A külső eszközök timeouttal és Linuxon folyamatonkénti memóriaplafonnal futnak, így egyetlen óriási szkennelt fájl sem dönti le a workert.
- Az eredmény megkülönbözteti a „nincs szöveg” és a „hiba” esetet. Egy szöveg nélküli fotó sikeres, üres eredmény, nem hiba. Egy timeout soha nem keveredik össze egy üres fájllal. A batch-feladat pontosan azokat a fájlokat próbálhatja újra, amelyeknél van értelme.
- Biztonságos ismeretlen feltöltésekre. A zip-bombát a ténylegesen kicsomagolt bájtok alapján állítja meg, nem a fájl által bevallott méret alapján. Külső DTD-t soha nem tölt be, a beágyazott DTD-t visszautasítja, így nincs XXE. A fájlnév soha nem jut el idézőjelezés nélkül egy shellhez.
- Tartalom alapú felismerés. A
jegyzet.txtnevű PDF is PDF-ként kerül feldolgozásra. - PHP 8.1+, keretrendszertől független.
Telepítés
composer require intragile/php-text-extractor
Kötelező az ext-mbstring. Ezen felül azt kell telepíteni, amit a formátumaid igényelnek:
| Formátum | Mi kell hozzá |
|---|---|
| TXT, MD, CSV, TSV, JSON, RTF | semmi más |
| XML | ext-xmlreader |
| HTML | ext-dom |
| DOCX, XLSX, PPTX, ODT, ODS, ODP | ext-zip, ext-xmlreader |
pdftotext (poppler-utils) |
|
| PNG, JPEG, TIFF, GIF, WebP, BMP | tesseract és a nyelvi adatai |
# Debian / Ubuntu (magyar OCR-rel)
apt-get install poppler-utils tesseract-ocr tesseract-ocr-hun tesseract-ocr-eng
# Alpine
apk add poppler-utils tesseract-ocr tesseract-ocr-data-hun tesseract-ocr-data-eng
# macOS
brew install poppler tesseract tesseract-lang
Hiányzó bővítmény vagy eszköz soha nem dob kivételt. Az adott formátumú fájlok ilyenkor Failed
státuszt kapnak, missing_extension vagy missing_tool okkal.
Az eredmény
Az extract(), az extractFromString() és az extractFromStream() mindig
IntrAgile\TextExtractor\Result-ot ad vissza, és a bemenettel kapcsolatos hiba miatt soha nem dob
kivételt.
| Property | Típus | |
|---|---|---|
status |
Status |
Extracted, Empty, NotExtractable vagy Failed |
text |
string |
UTF-8, egységes sorvégek; '', ha a státusz nem Extracted |
format |
?Format |
a felismert formátum, pl. Format::Docx |
reason |
?Reason |
miért nem sima siker |
message |
?string |
ember által olvasható részlet a naplóhoz |
truncated |
bool |
a kimeneti limit elérve; a csonkolt eredmény mindig Extracted |
| Status | Jelentés | Újrapróbálás? |
|---|---|---|
Extracted |
van szöveg | – |
Empty |
sikeres feldolgozás, a fájlban nincs szöveg | nem |
NotExtractable |
a fájl ebben a formában soha nem dolgozható fel | nem |
Failed |
technikai hiba, a fájl később még sikerülhet | igen |
| Reason | Status | Mikor |
|---|---|---|
unreadable |
Failed | a fájl nem létezik vagy nem olvasható |
missing_extension |
Failed | nincs telepítve az ext-zip, ext-xmlreader vagy ext-dom |
missing_tool |
Failed | nincs telepítve a pdftotext vagy a tesseract |
tool_timeout |
Failed | az eszköz túllépte a timeoutot |
parse_timeout |
Failed | a PHP-ban futó feldolgozás tovább tartott a maxParseSeconds-nál; a részleges szöveg elvész |
tool_failed |
Failed | az eszköz összeomlott, kifutott a memóriából vagy hibát jelzett |
internal_error |
Failed | egy extractor váratlan kivételt dobott (kérjük, jelezd) |
unsupported_format |
NotExtractable | ismeretlen formátum, régi .doc/.xls/.ppt, titkosított Office-fájl |
too_large |
NotExtractable | nagyobb a maxInputBytes-nál, vagy HTML és nagyobb a maxHtmlBytes-nál |
image_too_large |
NotExtractable | egy oldal vagy képkocka nagyobb a maxImagePixels-nél (az OCR indítása előtt ellenőrizve) |
encrypted |
NotExtractable | jelszóval védett PDF vagy OpenDocument |
corrupt |
NotExtractable | sérült vagy szerkezetileg hibás fájl, pl. egy .pdf nevű üres feltöltés |
archive_limit_exceeded |
NotExtractable | zip-bomba-korlát |
no_text_layer |
Empty | szövegréteg nélküli PDF, jellemzően szkennelt |
Bemenet
$extractor = TextExtractor::create();
$extractor->extract('/tmp/php3hX9a'); // fájlútvonal
$extractor->extract('/tmp/php3hX9a', 'export.csv'); // + eredeti fájlnév a .csv/.md/.json/... miatt
$extractor->extractFromString($bytes, 'jegyzet.txt');
$extractor->extractFromStream($stream, 'riport.pdf'); // az aktuális pozíciótól olvas
A bináris formátumokat a tartalmuk alapján ismeri fel, így a jegyzet.txt nevű PDF is PDF. Szöveges
tartalomnál a fájlnév dönti el, hogy TXT, Markdown, CSV, TSV, JSON, XML vagy HTML. A hibás
feltöltést is a fájlnév alapján veszi észre: egy .pdf, .docx, .png stb. nevű üres vagy csak
szöveget tartalmazó fájl NotExtractable/corrupt, nem üres szövegfájl. ext-zip nélkül a fájlnévből
dönti el azt is, melyik Office-formátum a zip-konténer.
Mit kapsz formátumonként
| Formátum | Kimenet |
|---|---|
| DOCX | törzs, táblázatok, szövegdobozok, láb- és végjegyzetek; a törölt változáskövetés és a mezőkódok kimaradnak |
| XLSX | minden munkalap ## Munkalap neve sorral, utána a nem üres sorok, a cellák tabulátorral (a cellán belüli sortörésből szóköz lesz); a képletek tárolt értéke; a dátum- vagy időformátumú számok ISO 8601 alakban (2026-11-05, 2026-11-05T10:30:00, 10:30:00); a számformátum dönti el, hogy a három közül melyik; az időtartamok (pl. [h]:mm) és a többi szám tárolt értékként |
| PPTX | diák a bemutató sorrendjében, üres sorral elválasztva (az üres bekezdés a dián belül nem ad sort); minden dia előadói jegyzete a dia szövege után következik, a jegyzetoldal fejléce, lábléce és diaszáma nélkül |
| ODT / ODS / ODP | mint a DOCX / XLSX / PPTX; a dátum- és időcellák ugyanabban az ISO 8601 jelölésben, mint az XLSX-nél, a tárolt értékből (az egy napnál hosszabb időtartam a megjelenített szövegével marad); a megjegyzések és a törölt változások kimaradnak; a sablonokat (OTT, OTS, OTP) és a Writer/Web dokumentumokat ugyanígy olvassa |
| RTF | szöveg bekezdésekkel; a betűtábla, a képek és az egyéb metaadatok kimaradnak |
| HTML | a látható szöveg, a blokkelemek külön sorban; a script és a style kimarad; a <head> címe az első |
| XML | a szöveg- és CDATA-tartalom |
| CSV / TSV / TXT / MD / JSON | maga a szöveg, UTF-8-ra alakítva |
a pdftotext kimenete, az oldalak között üres sor |
|
| Képek | Tesseract OCR a beállított nyelvekkel; a többoldalas TIFF minden oldala |
A DOCX, PPTX, ODT, ODP és HTML táblázatai úgy jönnek ki, mint a munkalapok sorai: soronként egy sor, a cellák tabulátorral elválasztva, a cellán belüli bekezdések szóközzel összefűzve.
Beállítások
use IntrAgile\TextExtractor\Options;
$extractor = TextExtractor::create(new Options(
ocrLanguages: 'hun+eng',
fallbackEncoding: 'Windows-1250',
processTimeout: 120,
));
| Option | Alapérték | |
|---|---|---|
maxInputBytes |
100 MiB | a nagyobb fájl too_large |
maxOutputBytes |
10 MiB | a hosszabb szöveget UTF-8-határon vágja, és beállítja a truncated-et |
maxArchiveEntryBytes |
256 MiB | egy zip-bejegyzésből kicsomagolható bájtok |
maxArchiveTotalBytes |
512 MiB | egy fájlból összesen kicsomagolható bájtok |
maxArchiveEntries |
10 000 | ennyi bejegyzés lehet egy zip-konténerben |
maxHtmlBytes |
10 MiB | a nagyobb HTML too_large; a libxml a DOM-ot natív memóriában, a memory_limit-en kívül építi fel: 10 MiB sűrű markup csúcsa Linuxon kb. 460 MB RSS (folyó szövegnél 160 MB) |
maxImagePixels |
16 000 000 | ha egy oldal vagy képkocka nagyobb, OCR előtt visszautasítja |
processTimeout |
60 s | külső eszközönként |
maxParseSeconds |
60 s | a PHP-ban futó feldolgozás (XML, OOXML, OpenDocument, RTF, szöveg, CSV, HTML) egy hívásra; felette az eredmény Failed / parse_timeout |
processMemoryMb |
512 | eszközönkénti címtér-plafon Linuxon; 0 kikapcsolja |
pdftotextBinary |
null |
útvonal vagy parancsnév; null esetén a PATH-ban keres |
tesseractBinary |
null |
útvonal vagy parancsnév; null esetén a PATH-ban keres |
ocrLanguages |
eng |
Tesseract-nyelvek +-szal; mindnek telepítve kell lennie |
fallbackEncoding |
Windows-1252 |
a nem érvényes UTF-8 szövegfájlokhoz; magyar exportokhoz Windows-1250. Bármely mbstring-kódolás, valamint a beépített Windows-1250, 1253, 1255–1258, CP874 és CP437 |
Az $options->with(['maxOutputBytes' => 1_000_000]) egy módosított másolatot ad vissza.
Saját extractor
Implementáld az IntrAgile\TextExtractor\Contracts\Extractor interfészt, és regisztráld. A
beépített extractorok előtt kerül sorra.
use IntrAgile\TextExtractor\Contracts\Extractor;
use IntrAgile\TextExtractor\Format;
use IntrAgile\TextExtractor\Options;
use IntrAgile\TextExtractor\Result;
final class CloudOcrExtractor implements Extractor
{
public function supports(Format $format): bool
{
return $format->isImage();
}
public function extract(string $path, Format $format, Options $options): Result
{
return Result::extracted($this->client->recognise($path), $format);
}
}
$extractor = TextExtractor::create()->withExtractor(new CloudOcrExtractor($client));
Nyers szöveget adj vissza: a normalizálást, a kimeneti limitet és az üres szöveg vizsgálatát a csomag
végzi. Sérült fájlnál dobj IntrAgile\TextExtractor\Exception\CorruptInput kivételt
(NotExtractable/corrupt); minden más kivételből Failed/internal_error lesz.
Biztonság
A csomagot ismeretlen forrásból származó feltöltésekhez tervezték:
- Zip-bomba. Minden zip-bejegyzést számláló streamen át olvas. A kicsomagolás a
maxArchiveEntryBytes/maxArchiveTotalBytesértéknél akkor is megáll, ha a fájl hazudik a méretéről. A..-ot tartalmazó vagy abszolút bejegyzésnevek soha nem kerülnek beolvasásra. - XML. A hálózati hozzáférés, a DTD-betöltés és az entitásfeloldás ki van kapcsolva. Az olyan
DOCTYPE-ot, amely csak egy külső DTD-t nevez meg (mint az Apple plist), elfogadja, mert azt a DTD-t
soha nem tölti be. A beágyazott subsetet tartalmazó DOCTYPE-ot, ahol entitásokat lehetne deklarálni,
corrupt-ként visszautasítja. Az útvonalak escape-eltfile://URI-ként jutnak a libxml-hez, így a fájlnévben lévő%41vagyphp://soha nem értelmeződik. - Külső eszközök. A parancs argumentumtömbből épül, a bemeneti útvonal mindig abszolút, így soha
nem olvasható opcióként. Amíg a memóriaplafon be van kapcsolva (alapértelmezés), az eszköz
/bin/sh -c 'ulimit -v …; exec …'alakban indul, minden argumentum aposztrófok között, vagyis a shell csak a limitet állítja be; a kernel Linuxon érvényesíti. Windowson vagyprocessMemoryMb: 0mellett nincs shell. Minden futásnak van timeoutja, korlátos kimenete és saját, üres munkakönyvtára, amelyet utána töröl. - CPU. A PHP-ban futó feldolgozás a
maxParseSecondsutánparse_timeout-tal leáll. A limitet a parser a lépések között ellenőrzi (4096 XML-node, RTF-token vagy HTML-node, illetve 64 KiB szöveg után), így a következő lépésnél lép érvénybe: egyetlen libxml-hívás nem szakítható meg. A libxml egy olvasáson belül négyzetes időben ellenőrzi az ismétlődő attribútumokat, így egy 1 MB-os, szándékosan összerakott nyitótag továbbra is több tíz másodpercig futhat, és a HTML DOM-má alakítása is egyetlen hívás. Ismeretlen fájlokat queue workerben dolgozz fel, olyan job-timeouttal, amely nagyobb amaxParseSeconds-nál és aprocessTimeout-nál. - Átmeneti fájlok a
sys_get_temp_dir()-ben jönnek létre, ésfinallyblokkban törlődnek.
Sebezhetőség bejelentéséhez lásd: SECURITY.md.
Összehasonlítás
- spatie/pdf-to-text: csak PDF; hibánál kivételt dob, így az üres és a hibás fájlt a hívónak kell szétválasztania.
- vaites/php-apache-tika: több formátum, de Java-alapú Tika-szerver kell hozzá.
- phpoffice/phpword és társai: teljes dokumentummodell olvasáshoz és íráshoz; az egész dokumentumot a memóriába töltik.
Licenc
MIT, lásd LICENSE.
Fejlesztő és karbantartó: IntrAgile Kft.
Ez az oldal a csomag v1.0.0 verziójának README-jéből készült. Forrás a GitHubonúj lapon nyílik meg