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

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:

💣 Descarga sistemas PHP y Laravel COMPLETOS GRATIS
Recursos premium listos para usar en proyectos reales 💻
👥 +1000 desarrolladores · 🔥 Publicaciones diarias
🔥 Unirme al canal GRATIS ahora 👥 Grupo de Facebook
⏳ Enlaces disponibles por tiempo limitado
💻 Sistemas completos 🔌 Plugins PRO ⚡ Scripts listos 🔥 Recursos exclusivos

  • 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.

¿De cuánta utilidad te ha parecido este contenido?

¡Haz clic en una estrella para puntuarlo!

Promedio de puntuación 0 / 5. Recuento de votos: 0

Hasta ahora, ¡no hay votos!. Sé el primero en puntuar este contenido.