ksergio.com

github-logo

  • Laravel
Publicado hace 1 día

Cómo leer datos de archivos Word, texto plano, PDF e imágenes en Laravel

Cómo leer datos de archivos Word, texto plano, PDF e imágenes en Laravel

En muchas aplicaciones es común tener que extraer contenido de diferentes tipos de archivos: documentos de Word, archivos de texto plano, PDFs o incluso imágenes que contienen texto. Para lograrlo, podemos aprovechar distintas librerías y técnicas de procesamiento de archivos en Laravel.

En este artículo vamos a ver cómo hacerlo, analizando paso a paso un ejemplo real basado en FilamentPHP y Laravel.

Detectar el tipo de archivo

El primer paso consiste en obtener el archivo subido por el usuario y determinar su tipo MIME. Esto nos dirá si se trata de un .txt, .pdf, .docx, .odt o una imagen.

Teniendo en cuenta que tengo guardado el fichero en mi disco local, podemos comprobar su mime con el Facade Storage tal que así:

$mime = Storage::disk('local')->mimeType($path);

Una vez identificado el tipo de archivo, aplicamos una estrategia distinta para extraer su contenido.

Leer archivos de texto plano

En el caso de los archivos .txt, basta con leer el contenido directamente desde el almacenamiento:

if ($mime === 'text/plain') {
    $file = Storage::disk('local')->get($data['file']);
    $content = $file;
}

Esto devuelve exactamente lo que está escrito en el archivo.

Extraer texto de PDFs

Para los archivos PDF, se puede utilizar la librería Smalot/PdfParser. Esta herramienta analiza la estructura interna del PDF y devuelve el texto:

elseif ($mime === 'application/pdf') {
    $parser = new Parser();
    $pdf = $parser->parseFile(storage_path('/app/private/' . $path));
    $content = $pdf->getText();
}

Es importante tener en cuenta que esta técnica funciona bien con PDFs que contienen texto, pero no con aquellos que son solo imágenes escaneadas (para eso usaremos OCR más adelante).

Leer documentos Word (.docx) y OpenDocument (.odt)

Los archivos .docx y .odt en realidad son archivos comprimidos ZIP que contienen varios XML internos. Dentro de ellos se encuentra el contenido del documento.

En el ejemplo, se usa ZipArchive para abrir el archivo y extraer el XML que corresponde al texto. De esta manera, podemos obtener texto plano eliminando etiquetas de estilo y estructura.

function extractTextFromDocument(string $filePath, string $extension): string
{
    $zip = new \ZipArchive();
    if ($zip->open($filePath) === true) {
        if ($extension === 'docx') {
            $index = $zip->locateName('word/document.xml');
            $xml = $zip->getFromIndex($index);
            $xml = str_replace(['<w:p>', '<w:br/>'], "\n", $xml);
            $text = strip_tags($xml);
        } elseif ($extension === 'odt') {
            $index = $zip->locateName('content.xml');
            $xml = $zip->getFromIndex($index);
            $xml = str_replace(['<text:p>', '<text:line-break/>'], "\n", $xml);
            $text = strip_tags($xml);
        }
        $zip->close();
    }
    return $text;
}

Reconocimiento de texto en imágenes (OCR)

Cuando el archivo es una imagen (image/*), necesitamos un motor de OCR (Reconocimiento Óptico de Caracteres). En este caso se utiliza Tesseract OCR junto con su wrapper en PHP. Esto permite extraer texto de fotografías, escaneos y capturas de pantalla.

elseif (str_starts_with($mime, 'image/')) {
    $fileFullPath = storage_path('/app/private/' . $path);

    try {
        $content = (new TesseractOCR($fileFullPath))
            ->lang('spa') // idioma español
            ->run();

        if (empty(trim($content))) {
            $content = '[No se pudo reconocer texto en la imagen]';
        }
    } catch (\thiagoalessio\TesseractOCR\UnsuccessfulCommandException $e) {
        $content = '[Error al procesar OCR: ' . $e->getMessage() . ']';
    }
}

Código completo

Debajo dejo el código completo que utilizo para un sistema simple en que uso todos estos apartados:

        $path = $data['file'];
        $mime = Storage::disk('local')->mimeType($path);
        $content = null;

        // Ficheros de texto plano
        if ($mime === 'text/plain') {
            $file = Storage::disk('local')->get($data['file']);
            $content = $file;
        }
        //Ficeros de tipo .pdf
        elseif ($mime === 'application/pdf') {
            $parser = new Parser();
            $pdf = $parser->parseFile(storage_path('/app/private/' . $path));
            $content = $pdf->getText();
        }
        // Ficheros de tipo .docx
        elseif ($mime === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document') {
            $content = $this->extractTextFromDocument(storage_path('/app/private/' . $path), 'docx');
        }
        // Ficheros de tipo .odt
        elseif ($mime === 'application/vnd.oasis.opendocument.text') {
            $content = $this->extractTextFromDocument(storage_path('/app/private/' . $path), 'odt');
        }
        // Ficheros de tipo image/*
        elseif (str_starts_with($mime, 'image/')) {
            $fileFullPath = storage_path('/app/private/' . $path);

            try {
                $content = (new TesseractOCR($fileFullPath))
                    ->lang('spa')
                    ->run();

                // Si no reconoce texto, podemos dejar un mensaje
                if (empty(trim($content))) {
                    $content = '[No se pudo reconocer texto en la imagen]';
                }
            } catch (\thiagoalessio\TesseractOCR\UnsuccessfulCommandException $e) {
                $content = '[Error al procesar OCR: ' . $e->getMessage() . ']';
            }
        }



        return File::create([
            'user_id' => auth()->id(),
            'name_original' => $data['file_original_name'], // ✅ nombre original
            'name_generated' => $data['file'],     // ✅ nombre generado
            'extension' => pathinfo($data['file'], PATHINFO_EXTENSION),
            'size' => Storage::disk('local')->size($data['file']),
            'content' => $content,
        ]);