← 首页

📋 HTML验证器

粘贴HTML代码,检查结构错误和问题

`; } function clearAll(){document.getElementById('htmlInput').value='';document.getElementById('results').style.display='none';} function validate(){ const html=document.getElementById('htmlInput').value; if(!html.trim()){garyyToast('请输入HTML代码');return;} const lines=html.split('\n'); const issues=[]; const tagStack=[]; const tagCount={}; lines.forEach((line,lineIdx)=>{ const lineNum=lineIdx+1; let cleaned=line.replace(//g,'').replace(/"([^"]*)"/g,'""').replace(/'([^']*)'/g,"''"); const openTags=cleaned.match(/<([a-zA-Z][a-zA-Z0-9]*)[\s>\/]/g)||[]; const closeTags=cleaned.match(/<\/([a-zA-Z][a-zA-Z0-9]*)\s*>/g)||[]; const selfClose=cleaned.match(/<[^>]*\/>/g)||[]; openTags.forEach(m=>{ const tag=m.match(/<([a-zA-Z][a-zA-Z0-9]*)/)[1].toLowerCase(); if(voidElements.includes(tag))return; if(selfClose.some(s=>s.includes(`<${tag}`)))return; if(tagStack.length>0&&tagStack[tagStack.length-1]===tag){ // 同行开启和关闭 }else{ tagStack.push(tag); tagCount[tag]=(tagCount[tag]||0)+1; } }); closeTags.forEach(m=>{ const tag=m.match(/<\/([a-zA-Z][a-zA-Z0-9]*)/)[1].toLowerCase(); if(tagStack.length===0){ issues.push({line:lineNum,type:'error',msg:`多余的结束标签 ,没有对应的开始标签`}); }else if(tagStack[tagStack.length-1]!==tag){ const expected=tagStack[tagStack.length-1]; issues.push({line:lineNum,type:'error',msg:`期望 但发现 ,标签未正确嵌套`}); }else{ tagStack.pop(); } }); deprecated.forEach(d=>{ if(new RegExp(`<${d}[\\s>]`,'gi').test(cleaned)){ issues.push({line:lineNum,type:'warn',msg:`使用了已废弃的 <${d}> 标签`}); } }); requiredAttrs&&Object.keys(requiredAttrs).forEach(tag=>{ const re=new RegExp(`<${tag}([\\s>][^>]*)?>`,'gi'); const match=cleaned.match(re); if(match){ const required=requiredAttrs[tag]; required.forEach(attr=>{ if(!new RegExp(`${attr}\\s*=`,`i`).test(match[0])){ issues.push({line:lineNum,type:'warn',msg:`<${tag}> 缺少必需属性 ${attr}`}); } }); } }); commonMistakes.forEach(cm=>{ if(cm.re.test(line)){ issues.push({line:lineNum,type:'info',msg:cm.issue}); } }); const inlineElements=['a','span','strong','em','b','i','u','img','br','hr','input','code','small','sub','sup']; const blockParents=['p','div','li','td','th','li','h1','h2','h3','h4','h5','h6']; blockParents.forEach(bp=>{ const re=new RegExp(`<${bp}[^>]*>([\\s\\S]*?)<(/${bp}|${inlineElements.join('|')})`,'gi'); if(re.test(cleaned)){ issues.push({line:lineNum,type:'info',msg:`块级元素 <${bp}> 内包含内联元素,注意语义`}); } }); }); while(tagStack.length>0){ const tag=tagStack.pop(); issues.push({line:lines.length,type:'error',msg:`缺少结束标签 `}); } if(!/<(!DOCTYPE|html)/i.test(html)){ issues.push({line:1,type:'warn',msg:'缺少 DOCTYPE 声明或 html 根元素'}); } if(!/]/i.test(html)){ issues.push({line:1,type:'warn',msg:'缺少 标签'}); } if(!/]/i.test(html)){ issues.push({line:1,type:'warn',msg:'缺少 标签'}); } const titleMatch=html.match(/(.*?)<\/title>/i); if(!titleMatch||!titleMatch[1].trim()){ issues.push({line:1,type:'info',msg:'建议添加有效的 <title> 标签'}); } const descMatch=html.match(/name=["']description["']/i); if(!descMatch){ issues.push({line:1,type:'info',msg:'建议添加 meta description 描述'}); } const viewportMatch=html.match(/name=["']viewport["']/i); if(!viewportMatch){ issues.push({line:1,type:'info',msg:'建议添加 viewport meta 标签以适配移动端'}); } showResults(issues); } function showResults(issues){ document.getElementById('results').style.display='block'; const errors=issues.filter(i=>i.type==='error').length; const warns=issues.filter(i=>i.type==='warn').length; const infos=issues.filter(i=>i.type==='info').length; let summaryHtml=''; if(errors===0&&warns===0)summaryHtml+=`<div class="summary-item s-pass">✅ 无错误</div>`; if(errors>0)summaryHtml+=`<div class="summary-item s-error">❌ ${errors} 个错误</div>`; if(warns>0)summaryHtml+=`<div class="summary-item s-warn">⚠️ ${warns} 个警告</div>`; if(infos>0)summaryHtml+=`<div class="summary-item s-info">💡 ${infos} 个建议</div>`; document.getElementById('summary').innerHTML=summaryHtml; let html=''; if(issues.length===0){ html='<div style="text-align:center;color:var(--green);padding:20px">🎉 未发现任何问题!HTML 结构看起来不错。</div>'; }else{ issues.forEach(i=>{ const icon=i.type==='error'?'❌':i.type==='warn'?'⚠️':'💡'; const cls=i.type==='error'?'msg-error':i.type==='warn'?'msg-warn':'msg-info'; html+=`<div class="result-item"><span class="icon">${icon}</span><span class="line-num">行 ${i.line}</span><span class="${cls}">${escapeHtml(i.msg)}</span></div>`; }); } document.getElementById('resultList').innerHTML=html; } </script> </body> </html>