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,optione similares) não geram erro se ficarem abertos, pois o HTML permite isso. - Conteúdo de
script,style,textareaetitleé 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> inesperadaPara 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> inesperadaPara 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> inesperadaPara 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