Ferramentas / Validador de HTML básico

Validador de HTML básico

Cole um trecho de HTML e veja tags não fechadas, mal aninhadas ou sobrando. É uma checagem simples de estrutura, não um validador completo.


Processamento local: nada do que você digita sai do seu navegador.

Como usar

Cole o HTML na caixa. A cada tecla, um verificador próprio percorre as tags e aponta, com linha e coluna: tags de fechamento sem abertura, tags fechadas fora de ordem (como <b><i></b></i>), tags que nunca foram fechadas, atributos repetidos, aspas não fechadas, comentários abertos e fechamento de elementos vazios (como </br>).

Limitações (leia antes de confiar)

  • Isto não é o validador oficial do W3C. Não confere se a tag ou o atributo existe, nem se um elemento pode ficar dentro do outro (por exemplo <div> dentro de <p>).
  • Elementos com fechamento opcional (p, li, td, tr, option e similares) não geram erro se ficarem abertos, pois o HTML permite isso.
  • Conteúdo de script, style, textarea e title é ignorado, e um "<" solto no texto é tratado como texto.
  • Navegadores corrigem HTML quebrado em silêncio; por isso uma página pode parecer certa mesmo com erros.

Como implementar em código

Exemplos prontos para usar no seu projeto.

const VAZIAS = new Set(['br','img','hr','input','meta','link','area','base','col','embed','source','track','wbr','param']);

// Verificação simples de abertura/fechamento de tags (ignora comentários e <script>)
function verificarHtml(html) {
  const re = /<(\/?)([A-Za-z][A-Za-z0-9-]*)[^>]*?(\/?)>/g;
  const pilha = [];
  let m;
  while ((m = re.exec(html))) {
    const [, fecha, nome, auto] = m, tag = nome.toLowerCase();
    if (VAZIAS.has(tag) || auto) continue;
    const linha = html.slice(0, m.index).split('\n').length;
    if (!fecha) pilha.push({ tag, linha });
    else if (!pilha.length || pilha[pilha.length - 1].tag !== tag) return `linha ${linha}: </${tag}> inesperada`;
    else pilha.pop();
  }
  return pilha.length ? `<${pilha.at(-1).tag}> aberta na linha ${pilha.at(-1).linha} nunca foi fechada` : null;
}

console.log(verificarHtml('<div><p>ok</p></div>'));   // null
console.log(verificarHtml('<b><i>x</b></i>'));        // linha 1: </b> inesperada

Para HTML real use um parser completo (ex.: parse5 ou o DOMParser); esta função é só didática.

import re

VAZIAS = {"br", "img", "hr", "input", "meta", "link", "area", "base", "col", "embed", "source", "track", "wbr", "param"}
TAG = re.compile(r"<(/?)([A-Za-z][A-Za-z0-9-]*)[^>]*?(/?)>")

def verificar_html(html: str):
    """Verificação simples de abertura/fechamento de tags (ignora comentários e <script>)."""
    pilha = []
    for m in TAG.finditer(html):
        fecha, nome, auto = m.group(1), m.group(2).lower(), m.group(3)
        if nome in VAZIAS or auto:
            continue
        linha = html.count("\n", 0, m.start()) + 1
        if not fecha:
            pilha.append((nome, linha))
        elif not pilha or pilha[-1][0] != nome:
            return f"linha {linha}: </{nome}> inesperada"
        else:
            pilha.pop()
    return f"<{pilha[-1][0]}> aberta na linha {pilha[-1][1]} nunca foi fechada" if pilha else None

print(verificar_html("<div><p>ok</p></div>"))  # None
print(verificar_html("<b><i>x</b></i>"))       # linha 1: </b> inesperada

Para HTML real use html.parser (biblioteca padrão), BeautifulSoup ou lxml.

package main

import (
	"fmt"
	"regexp"
	"strings"
)

var tag = regexp.MustCompile(`<(/?)([A-Za-z][A-Za-z0-9-]*)[^>]*?(/?)>`)
var vazias = map[string]bool{"br": true, "img": true, "hr": true, "input": true, "meta": true, "link": true,
	"area": true, "base": true, "col": true, "embed": true, "source": true, "track": true, "wbr": true, "param": true}

type aberta struct {
	nome  string
	linha int
}

// Verificação simples de abertura/fechamento de tags (ignora comentários e <script>)
func verificarHTML(html string) string {
	var pilha []aberta
	for _, m := range tag.FindAllStringSubmatchIndex(html, -1) {
		fecha, nome, auto := html[m[2]:m[3]], strings.ToLower(html[m[4]:m[5]]), html[m[6]:m[7]]
		if vazias[nome] || auto != "" {
			continue
		}
		linha := strings.Count(html[:m[0]], "\n") + 1
		if fecha == "" {
			pilha = append(pilha, aberta{nome, linha})
		} else if len(pilha) == 0 || pilha[len(pilha)-1].nome != nome {
			return fmt.Sprintf("linha %d: </%s> inesperada", linha, nome)
		} else {
			pilha = pilha[:len(pilha)-1]
		}
	}
	if len(pilha) > 0 {
		t := pilha[len(pilha)-1]
		return fmt.Sprintf("<%s> aberta na linha %d nunca foi fechada", t.nome, t.linha)
	}
	return ""
}

func main() {
	fmt.Printf("%q\n", verificarHTML("<div><p>ok</p></div>")) // ""
	fmt.Println(verificarHTML("<b><i>x</b></i>"))            // linha 1: </b> inesperada
}

Para HTML real use golang.org/x/net/html.

final tag = RegExp(r'<(/?)([A-Za-z][A-Za-z0-9-]*)[^>]*?(/?)>');
const vazias = {'br', 'img', 'hr', 'input', 'meta', 'link', 'area', 'base', 'col', 'embed', 'source', 'track', 'wbr', 'param'};

// Verificação simples de abertura/fechamento de tags (ignora comentários e <script>)
String? verificarHtml(String html) {
  final pilha = <(String, int)>[];
  for (final m in tag.allMatches(html)) {
    final fecha = m.group(1)!, nome = m.group(2)!.toLowerCase(), auto = m.group(3)!;
    if (vazias.contains(nome) || auto.isNotEmpty) continue;
    final linha = '\n'.allMatches(html.substring(0, m.start)).length + 1;
    if (fecha.isEmpty) {
      pilha.add((nome, linha));
    } else if (pilha.isEmpty || pilha.last.$1 != nome) {
      return 'linha $linha: </$nome> inesperada';
    } else {
      pilha.removeLast();
    }
  }
  return pilha.isEmpty ? null : '<${pilha.last.$1}> aberta na linha ${pilha.last.$2} nunca foi fechada';
}

void main() {
  print(verificarHtml('<div><p>ok</p></div>')); // null
  print(verificarHtml('<b><i>x</b></i>'));      // linha 1: </b> inesperada
}
import java.util.*;
import java.util.regex.*;

public class HtmlCheck {
    static final Set<String> VAZIAS = Set.of("br","img","hr","input","meta","link","area","base","col","embed","source","track","wbr","param");
    static final Pattern TAG = Pattern.compile("<(/?)([A-Za-z][A-Za-z0-9-]*)[^>]*?(/?)>");

    // Verificação simples de abertura/fechamento de tags (ignora comentários e <script>)
    static String verificar(String html) {
        Deque<String[]> pilha = new ArrayDeque<>();
        Matcher m = TAG.matcher(html);
        while (m.find()) {
            String nome = m.group(2).toLowerCase();
            if (VAZIAS.contains(nome) || !m.group(3).isEmpty()) continue;
            int linha = (int) html.substring(0, m.start()).chars().filter(c -> c == '\n').count() + 1;
            if (m.group(1).isEmpty()) pilha.push(new String[]{nome, String.valueOf(linha)});
            else if (pilha.isEmpty() || !pilha.peek()[0].equals(nome)) return "linha " + linha + ": </" + nome + "> inesperada";
            else pilha.pop();
        }
        return pilha.isEmpty() ? null : "<" + pilha.peek()[0] + "> aberta na linha " + pilha.peek()[1] + " nunca foi fechada";
    }

    public static void main(String[] args) {
        System.out.println(verificar("<div><p>ok</p></div>")); // null
        System.out.println(verificar("<b><i>x</b></i>"));      // linha 1: </b> inesperada
    }
}

Para HTML real use a biblioteca jsoup.

<?php
// Verificação simples de abertura/fechamento de tags (ignora comentários e <script>)
function verificarHtml(string $html): ?string {
    $vazias = ['br','img','hr','input','meta','link','area','base','col','embed','source','track','wbr','param'];
    preg_match_all('~<(/?)([A-Za-z][A-Za-z0-9-]*)[^>]*?(/?)>~', $html, $ms, PREG_SET_ORDER | PREG_OFFSET_CAPTURE);
    $pilha = [];
    foreach ($ms as $m) {
        $nome = strtolower($m[2][0]);
        if (in_array($nome, $vazias, true) || $m[3][0] !== '') continue;
        $linha = substr_count(substr($html, 0, $m[0][1]), "\n") + 1;
        if ($m[1][0] === '') {
            $pilha[] = [$nome, $linha];
        } elseif (!$pilha || end($pilha)[0] !== $nome) {
            return "linha $linha: </$nome> inesperada";
        } else {
            array_pop($pilha);
        }
    }
    return $pilha ? '<' . end($pilha)[0] . '> aberta na linha ' . end($pilha)[1] . ' nunca foi fechada' : null;
}

var_dump(verificarHtml('<div><p>ok</p></div>')); // NULL
echo verificarHtml('<b><i>x</b></i>');            // linha 1: </b> inesperada

Para HTML real use DOMDocument::loadHTML com libxml_use_internal_errors(true) e libxml_get_errors().

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;

class Program
{
    static readonly HashSet<string> Vazias = new() { "br","img","hr","input","meta","link","area","base","col","embed","source","track","wbr","param" };
    static readonly Regex Tag = new(@"<(/?)([A-Za-z][A-Za-z0-9-]*)[^>]*?(/?)>");

    // Verificação simples de abertura/fechamento de tags (ignora comentários e <script>)
    static string? Verificar(string html)
    {
        var pilha = new Stack<(string Nome, int Linha)>();
        foreach (Match m in Tag.Matches(html))
        {
            var nome = m.Groups[2].Value.ToLower();
            if (Vazias.Contains(nome) || m.Groups[3].Value != "") continue;
            int linha = html.Substring(0, m.Index).Count(c => c == '\n') + 1;
            if (m.Groups[1].Value == "") pilha.Push((nome, linha));
            else if (pilha.Count == 0 || pilha.Peek().Nome != nome) return $"linha {linha}: </{nome}> inesperada";
            else pilha.Pop();
        }
        return pilha.Count == 0 ? null : $"<{pilha.Peek().Nome}> aberta na linha {pilha.Peek().Linha} nunca foi fechada";
    }

    static void Main()
    {
        Console.WriteLine(Verificar("<div><p>ok</p></div>") ?? "ok");
        Console.WriteLine(Verificar("<b><i>x</b></i>"));
    }
}

Para HTML real use o pacote HtmlAgilityPack (HtmlDocument.ParseErrors).

Perguntas frequentes

Esta ferramenta substitui o validador do W3C?

Não. Ela só confere a estrutura das tags (abertura, fechamento e aninhamento). Para validar contra a especificação completa use o validador oficial do W3C.

Por que um <li> sem </li> não dá erro?

Na especificação do HTML a tag de fechamento de li, p, td, tr e alguns outros é opcional, então deixá-la de fora é válido. Mesmo assim, fechar explicitamente deixa o código mais claro.

Meu HTML é enviado a algum servidor?

Não. A verificação é feita por JavaScript no seu navegador.

Veja também

Precisa de um sistema ou app sob medida?

A VcInova desenvolve software, aplicativos e integrações para empresas de todo o Brasil.

Fale com a VcInova