Lenguaje PHP

Cómo limpiar tablas HTML con PHP (guía paso a paso)

En muchos proyectos web nos encontramos con tablas HTML llenas de atributos innecesarios como class, id o style. Además, a veces incluyen columnas que no queremos mostrar, como la clásica columna “Acciones” en sistemas de inventario o administración.
Con este tutorial se aprendera a limpiar tablas HTML con PHP de una manera sencilla.

Cómo limpiar tablas HTML con PHP (guía paso a paso)

En este tutorial aprenderás a:

  • Eliminar clases, estilos e identificadores de tablas HTML.
  • Quitar columnas específicas (como “Acciones”).
  • Generar un archivo limpio en HTML o TXT desde el navegador.
Cómo limpiar tablas HTML con PHP (guía paso a paso)
Cómo limpiar tablas HTML con PHP (guía paso a paso)

¿Por qué limpiar una tabla HTML?

Una tabla HTML puede venir de un exportador automático (ej. sistemas de ventas, inventarios, CMS) y contener:

  • Estilos en línea (style="...") que rompen el diseño responsive.
  • Clases e identificadores heredados de frameworks como Bootstrap o DataTables.
  • Columnas innecesarias (acciones, botones, iconos).

Limpiarla ayuda a:

  • Mejorar la accesibilidad.
  • Optimizar para SEO (tablas más ligeras, sin basura de código).
  • Preparar datos para exportación a Excel, CSV o TXT.

Script PHP para limpiar tablas HTML

Aquí un ejemplo funcional en PHP que procesa un archivo con una tabla y genera una versión limpia:

<?php
// limpiar_tabla_web.php
// Versión robusta: sube archivo, limpia tablas, guarda HTML y TXT en ./downloads/ y muestra enlaces.
// Mostrar errores para depuración (quítalo en producción)
ini_set('display_errors', 1);
error_reporting(E_ALL);


/**
 * Normaliza texto: decodifica entidades, colapsa espacios y trim.
 */
function normalize_text(string $s): string {
    $s = html_entity_decode($s, ENT_QUOTES | ENT_HTML5, 'UTF-8');
    $s = preg_replace('/[\r\n\t]+/', ' ', $s);
    $s = preg_replace('/\s+/', ' ', $s);
    return trim($s);
}


/**
 * Construye URL absoluta al recurso en downloads/
 */
function build_download_url($filename) {
    $scheme = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
    $host = $_SERVER['HTTP_HOST'];
    // dirname puede devolver '\' en Windows; normalizamos
    $dir = rtrim(str_replace('\\', '/', dirname($_SERVER['REQUEST_URI'])), '/');
    return "{$scheme}://{$host}{$dir}/downloads/{$filename}";
}


$messages = [];


if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['archivo'])) {
    // Validar subida
    $fileErr = $_FILES['archivo']['error'] ?? UPLOAD_ERR_NO_FILE;
    if ($fileErr !== UPLOAD_ERR_OK) {
        $messages[] = "Error en la subida: código $fileErr. Verifica upload_max_filesize y post_max_size en php.ini.";
    } else {
        $tmpName = $_FILES['archivo']['tmp_name'];
        if (!is_uploaded_file($tmpName)) {
            $messages[] = "Archivo no válido (is_uploaded_file falló).";
        } else {
            $removeActions = isset($_POST['remove_actions']);


            $raw = file_get_contents($tmpName);
            if ($raw === false) {
                $messages[] = "No se pudo leer el archivo temporal.";
            } else {
                // Preparar DOM
                libxml_use_internal_errors(true);
                $dom = new DOMDocument('1.0', 'UTF-8');
                $raw_html = mb_convert_encoding($raw, 'HTML-ENTITIES', 'UTF-8');


                // Flags opcionales (si no existen en la versión PHP, loadHTML igualmente funcionará)
                $flags = 0;
                if (defined('LIBXML_HTML_NOIMPLIED') && defined('LIBXML_HTML_NODEFDTD')) {
                    $flags = LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD;
                }


                $loaded = @$dom->loadHTML('<?xml encoding="utf-8" ?>' . $raw_html, $flags);
                $libErrors = libxml_get_errors();
                libxml_clear_errors();
                if (!$loaded) {
                    $messages[] = "DOMDocument::loadHTML falló. Errores: " . print_r($libErrors, true);
                } else {
                    $xpath = new DOMXPath($dom);
                    $tables = $xpath->query('//table');


                    if ($tables->length === 0) {
                        $messages[] = "No se encontró ninguna <table> en el archivo.";
                    } else {
                        // Preparar carpeta downloads/
                        $downloadsDir = __DIR__ . '/downloads';
                        if (!is_dir($downloadsDir)) {
                            if (!mkdir($downloadsDir, 0755, true)) {
                                $messages[] = "No se pudo crear la carpeta downloads/. Revisa permisos.";
                            }
                        }
                        if (!is_writable($downloadsDir)) {
                            $messages[] = "La carpeta downloads/ no es escribible. Ajusta permisos (chmod).";
                        }


                        $htmlOutputs = [];
                        $tsvLines = [];


                        // Procesar cada tabla
                        foreach ($tables as $tableIndex => $table) {
                            // Recolectar TRs que pertenezcan directamente a esta tabla (evita tablas anidadas)
                            $trs = [];
                            foreach ($table->getElementsByTagName('tr') as $tr) {
                                $cur = $tr;
                                $inSameTable = false;
                                while ($cur = $cur->parentNode) {
                                    if ($cur === $table) { $inSameTable = true; break; }
                                    if (strtolower($cur->nodeName) === 'table') break;
                                }
                                if (!$inSameTable) continue;


                                // Obtener celdas directas (td/th)
                                $cells = [];
                                foreach ($tr->childNodes as $child) {
                                    if ($child->nodeType === XML_ELEMENT_NODE) {
                                        $nm = strtolower($child->nodeName);
                                        if ($nm === 'td' || $nm === 'th') $cells[] = $child;
                                    }
                                }
                                // Fallback si no hay celdas directas
                                if (count($cells) === 0) {
                                    foreach ($tr->getElementsByTagName('th') as $c) $cells[] = $c;
                                    foreach ($tr->getElementsByTagName('td') as $c) $cells[] = $c;
                                }


                                if (count($cells) > 0) $trs[] = $cells;
                            }


                            if (count($trs) === 0) continue; // tabla sin filas útiles


                            // Detectar fila cabecera (thead o primer tr con <th>)
                            $headerRow = null;
                            // preferir THEAD
                            $thead = $table->getElementsByTagName('thead');
                            if ($thead->length > 0) {
                                foreach ($thead->item(0)->getElementsByTagName('tr') as $trCandidate) {
                                    $headerRow = $trCandidate;
                                    break;
                                }
                            }
                            if (!$headerRow) {
                                // buscar primer TR que contenga <th>
                                foreach ($table->getElementsByTagName('tr') as $trCandidate) {
                                    foreach ($trCandidate->childNodes as $c) {
                                        if ($c->nodeType === XML_ELEMENT_NODE && strtolower($c->nodeName) === 'th') {
                                            $headerRow = $trCandidate;
                                            break 2;
                                        }
                                    }
                                }
                            }


                            // Si no hay headerRow, no hay eliminación por nombre de columna
                            $removeIndexes = [];
                            if ($removeActions && $headerRow) {
                                // identificar índices de "Acciones" (case-insensitive)
                                $ci = 0;
                                foreach ($headerRow->childNodes as $c) {
                                    if ($c->nodeType !== XML_ELEMENT_NODE) continue;
                                    $nm = strtolower($c->nodeName);
                                    if ($nm !== 'th' && $nm !== 'td') continue;
                                    $txt = normalize_text($c->textContent);
                                    if (stripos($txt, 'acciones') !== false) {
                                        $removeIndexes[] = $ci;
                                    }
                                    $ci++;
                                }
                            }


                            // Reconstruir tabla limpia
                            $out = new DOMDocument('1.0', 'UTF-8');
                            $out->formatOutput = false;
                            $newTable = $out->createElement('table');
                            $out->appendChild($newTable);


                            foreach ($trs as $rowCells) {
                                $newTr = $out->createElement('tr');
                                $rowTextParts = [];
                                $colIndex = 0;
                                foreach ($rowCells as $cell) {
                                    if (in_array($colIndex, $removeIndexes, true)) { $colIndex++; continue; }


                                    $tag = (strtolower($cell->nodeName) === 'th') ? 'th' : 'td';
                                    $newCell = $out->createElement($tag);
                                    $text = normalize_text($cell->textContent);
                                    $newCell->appendChild($out->createTextNode($text));
                                    $newTr->appendChild($newCell);
                                    $rowTextParts[] = $text;
                                    $colIndex++;
                                }
                                if ($newTr->hasChildNodes()) {
                                    $newTable->appendChild($newTr);
                                    if (count($rowTextParts) > 0) $tsvLines[] = implode("\t", $rowTextParts);
                                }
                            }


                            $htmlOutputs[] = $out->saveHTML($newTable);
                        } // end foreach tables


                        if (count($htmlOutputs) === 0) {
                            $messages[] = "Se encontraron tablas pero ninguna produjo salida limpia.";
                        } else {
                            // Guardar archivos en downloads/
                            $baseHtmlName = 'tabla_limpia_' . time() . '_' . bin2hex(random_bytes(4)) . '.html';
                            $baseTxtName  = 'tabla_limpia_' . time() . '_' . bin2hex(random_bytes(4)) . '.txt';
                            $htmlPath = $downloadsDir . '/' . $baseHtmlName;
                            $txtPath  = $downloadsDir . '/' . $baseTxtName;


                            $fullHtmlContent = implode("\n\n", $htmlOutputs);
                            $okHtml = @file_put_contents($htmlPath, $fullHtmlContent);
                            $okTxt  = @file_put_contents($txtPath, implode(PHP_EOL, $tsvLines));


                            if ($okHtml === false || $okTxt === false) {
                                $messages[] = "Error al guardar archivos en downloads/. Revisa permisos. file_put_contents devolvió false.";
                            } else {
                                $downloadHtmlUrl = build_download_url($baseHtmlName);
                                $downloadTxtUrl  = build_download_url($baseTxtName);
                                $messages[] = "Ficheros generados correctamente:";
                                $messages[] = "<a href=\"{$downloadHtmlUrl}\" target=\"_blank\">Descargar HTML limpio</a>";
                                $messages[] = "<a href=\"{$downloadTxtUrl}\" target=\"_blank\">Descargar TXT (TSV)</a>";
                                // También mostrar vista previa limpia
                                $preview = htmlspecialchars($fullHtmlContent, ENT_QUOTES | ENT_HTML5, 'UTF-8');
                            }
                        }
                    }
                }
            }
        }
    }
}
?>
<!doctype html>
<html lang="es">
<head>
  <meta charset="utf-8">
  <title>Limpiar tabla - Web</title>
  <style>
    body{font-family:system-ui,Segoe UI,Roboto,Arial;max-width:900px;margin:20px auto;padding:10px;}
    .msg{background:#f7f7f7;border-left:4px solid #888;padding:8px;margin:8px 0;}
    .err{border-left-color:#c33;background:#fff0f0;}
    .ok{border-left-color:#2a8;background:#f0fff6;}
    pre.preview{background:#111;color:#dcdcdc;padding:10px;overflow:auto;white-space:pre-wrap;}
  </style>
</head>
<body>
  <h2>Limpiar tabla (web)</h2>


  <form method="post" enctype="multipart/form-data">
    <label>Archivo (HTML / TXT que contenga la tabla):</label><br>
    <input type="file" name="archivo" accept=".html,.htm,.txt" required><br><br>
    <label><input type="checkbox" name="remove_actions"> Eliminar columna "Acciones"</label><br><br>
    <button type="submit">Procesar y generar ficheros</button>
  </form>


  <hr>


  <?php
  if (!empty($messages)) {
      foreach ($messages as $m) {
          $isLink = (strpos($m, '<a ') !== false);
          $cls = $isLink ? 'ok' : 'msg';
          echo "<div class=\"{$cls}\">";
          echo $isLink ? $m : htmlspecialchars($m, ENT_QUOTES | ENT_HTML5, 'UTF-8');
          echo "</div>";
      }
  }


  if (!empty($libErrors)) {
      echo "<div class=\"msg err\"><strong>Warnings/parsing:</strong><pre>" . htmlspecialchars(print_r($libErrors, true)) . "</pre></div>";
  }


  if (!empty($preview)) {
      echo "<h3>Vista previa (HTML limpio):</h3>";
      echo "<div style='border:1px solid #ddd;padding:8px;margin-bottom:12px;'>";
      // mostramos el HTML limpio interpretado
      echo $fullHtmlContent;
      echo "</div>";
      echo "<h4>HTML como texto (para copiar/pegar):</h4>";
      echo "<pre class='preview'>" . $preview . "</pre>";
  }
  ?>


  <hr>
  <small>Si no se crean los ficheros:
    <ul>
      <li>Revisa permisos: la carpeta <code>downloads/</code> debe ser escribible por el proceso web (chmod 755 o 775; si nada funciona prueba 0777 temporalmente).</li>
      <li>Revisa <code>upload_max_filesize</code> y <code>post_max_size</code> en <code>php.ini</code> si la subida falla.</li>
      <li>Revisa logs de PHP/Apache para errores (error_log).</li>
    </ul>
  </small>
</body>
</html>

Este script:

  • Carga la tabla original.
  • Elimina atributos (class, style, id).
  • Convierte elementos no permitidos (botones, imágenes) en texto.
  • Devuelve un archivo descargable limpio.

Cómo usarlo en el navegador

  1. Guarda el código como limpiar_tabla.php.
  2. Súbelo a tu servidor local (ej. XAMPP, Laragon) o hosting.
  3. Crea un formulario HTML para subir el archivo con la tabla:
    <form method="post" enctype="multipart/form-data" action="limpiar_tabla.php">
    <input type="file" name="archivo" accept=".html,.txt" required>
    <label><input type="checkbox" name="remove_actions"> Eliminar columna "Acciones"</label>
    <button type="submit">Procesar</button>
    </form>
  4. Sube tu archivo con la tabla y descarga la versión limpia.

Ejemplo práctico: quitar atributos class id style en tabla HTML

Puedes usar un fichero con extension html o txt y dentro el contenido de una tabla con clases, ids, styles en linea, etc.
Esto vendria a ser la entrada:

<table id="example" class="table" style="width:100%">
<tr><th>Código</th><th>Producto</th><th>Acciones</th></tr>
<tr><td>001</td><td>Tela Azul</td><td><button>Ver</button></td></tr>
</table>

La salida despues de procesar la información

<table>
<tr><th>Código</th><th>Producto</th></tr>
<tr><td>001</td><td>Tela Azul</td></tr>
</table>

En resumen, este script nos da la opcion de cargar un fichero html o txt con una tabla que contiene cientos o miles de registros y el cual podra limpiar y dejarlo listo.

Mostrar más

Nestor Tapia

Bloggero, amante de la programación PHP, innovador y me fascina compartir información. Desde que conocí el entorno informatico y el internet me llamó la atención la programación, Por tal motivo he creado mi blog BAULPHP.COM para compartir mis experiencias con todos ustedes. ¡Gracias por leerme!.
Botón volver arriba
Esta web utiliza cookies propias para su correcto funcionamiento. Contiene enlaces a sitios web de terceros con políticas de privacidad ajenas que podrás aceptar o no cuando accedas a ellos. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos.
Privacidad